rs-test-templates.js (23854B)
1 'use strict'; 2 3 // These tests can be run against any readable stream produced by the web platform that meets the given descriptions. 4 // For readable stream tests, the factory should return the stream. For reader tests, the factory should return a 5 // { stream, reader } object. (You can use this to vary the time at which you acquire a reader.) 6 7 self.templatedRSEmpty = (label, factory) => { 8 test(() => {}, 'Running templatedRSEmpty with ' + label); 9 10 test(() => { 11 12 const rs = factory(); 13 14 assert_equals(typeof rs.locked, 'boolean', 'has a boolean locked getter'); 15 assert_equals(typeof rs.cancel, 'function', 'has a cancel method'); 16 assert_equals(typeof rs.getReader, 'function', 'has a getReader method'); 17 assert_equals(typeof rs.pipeThrough, 'function', 'has a pipeThrough method'); 18 assert_equals(typeof rs.pipeTo, 'function', 'has a pipeTo method'); 19 assert_equals(typeof rs.tee, 'function', 'has a tee method'); 20 21 }, label + ': instances have the correct methods and properties'); 22 23 test(() => { 24 const rs = factory(); 25 26 assert_throws_js(TypeError, () => rs.getReader({ mode: '' }), 'empty string mode should throw'); 27 assert_throws_js(TypeError, () => rs.getReader({ mode: null }), 'null mode should throw'); 28 assert_throws_js(TypeError, () => rs.getReader({ mode: 'asdf' }), 'asdf mode should throw'); 29 assert_throws_js(TypeError, () => rs.getReader(5), '5 should throw'); 30 31 // Should not throw 32 rs.getReader(null); 33 34 }, label + ': calling getReader with invalid arguments should throw appropriate errors'); 35 }; 36 37 self.templatedRSClosed = (label, factory) => { 38 test(() => {}, 'Running templatedRSClosed with ' + label); 39 40 promise_test(() => { 41 42 const rs = factory(); 43 const cancelPromise1 = rs.cancel(); 44 const cancelPromise2 = rs.cancel(); 45 46 assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises'); 47 48 return Promise.all([ 49 cancelPromise1.then(v => assert_equals(v, undefined, 'first cancel() call should fulfill with undefined')), 50 cancelPromise2.then(v => assert_equals(v, undefined, 'second cancel() call should fulfill with undefined')) 51 ]); 52 53 }, label + ': cancel() should return a distinct fulfilled promise each time'); 54 55 test(() => { 56 57 const rs = factory(); 58 assert_false(rs.locked, 'locked getter should return false'); 59 60 }, label + ': locked should be false'); 61 62 test(() => { 63 64 const rs = factory(); 65 rs.getReader(); // getReader() should not throw. 66 67 }, label + ': getReader() should be OK'); 68 69 test(() => { 70 71 const rs = factory(); 72 73 const reader = rs.getReader(); 74 reader.releaseLock(); 75 76 const reader2 = rs.getReader(); // Getting a second reader should not throw. 77 reader2.releaseLock(); 78 79 rs.getReader(); // Getting a third reader should not throw. 80 81 }, label + ': should be able to acquire multiple readers if they are released in succession'); 82 83 test(() => { 84 85 const rs = factory(); 86 87 rs.getReader(); 88 89 assert_throws_js(TypeError, () => rs.getReader(), 'getting a second reader should throw'); 90 assert_throws_js(TypeError, () => rs.getReader(), 'getting a third reader should throw'); 91 92 }, label + ': should not be able to acquire a second reader if we don\'t release the first one'); 93 }; 94 95 self.templatedRSErrored = (label, factory, error) => { 96 test(() => {}, 'Running templatedRSErrored with ' + label); 97 98 promise_test(t => { 99 100 const rs = factory(); 101 const reader = rs.getReader(); 102 103 return Promise.all([ 104 promise_rejects_exactly(t, error, reader.closed), 105 promise_rejects_exactly(t, error, reader.read()) 106 ]); 107 108 }, label + ': getReader() should return a reader that acts errored'); 109 110 promise_test(t => { 111 112 const rs = factory(); 113 const reader = rs.getReader(); 114 115 return Promise.all([ 116 promise_rejects_exactly(t, error, reader.read()), 117 promise_rejects_exactly(t, error, reader.read()), 118 promise_rejects_exactly(t, error, reader.closed) 119 ]); 120 121 }, label + ': read() twice should give the error each time'); 122 123 test(() => { 124 const rs = factory(); 125 126 assert_false(rs.locked, 'locked getter should return false'); 127 }, label + ': locked should be false'); 128 }; 129 130 self.templatedRSErroredSyncOnly = (label, factory, error) => { 131 test(() => {}, 'Running templatedRSErroredSyncOnly with ' + label); 132 133 promise_test(t => { 134 135 const rs = factory(); 136 rs.getReader().releaseLock(); 137 const reader = rs.getReader(); // Calling getReader() twice does not throw (the stream is not locked). 138 139 return promise_rejects_exactly(t, error, reader.closed); 140 141 }, label + ': should be able to obtain a second reader, with the correct closed promise'); 142 143 test(() => { 144 145 const rs = factory(); 146 rs.getReader(); 147 148 assert_throws_js(TypeError, () => rs.getReader(), 'getting a second reader should throw a TypeError'); 149 assert_throws_js(TypeError, () => rs.getReader(), 'getting a third reader should throw a TypeError'); 150 151 }, label + ': should not be able to obtain additional readers if we don\'t release the first lock'); 152 153 promise_test(t => { 154 155 const rs = factory(); 156 const cancelPromise1 = rs.cancel(); 157 const cancelPromise2 = rs.cancel(); 158 159 assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises'); 160 161 return Promise.all([ 162 promise_rejects_exactly(t, error, cancelPromise1), 163 promise_rejects_exactly(t, error, cancelPromise2) 164 ]); 165 166 }, label + ': cancel() should return a distinct rejected promise each time'); 167 168 promise_test(t => { 169 170 const rs = factory(); 171 const reader = rs.getReader(); 172 const cancelPromise1 = reader.cancel(); 173 const cancelPromise2 = reader.cancel(); 174 175 assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises'); 176 177 return Promise.all([ 178 promise_rejects_exactly(t, error, cancelPromise1), 179 promise_rejects_exactly(t, error, cancelPromise2) 180 ]); 181 182 }, label + ': reader cancel() should return a distinct rejected promise each time'); 183 }; 184 185 self.templatedRSEmptyReader = (label, factory) => { 186 test(() => {}, 'Running templatedRSEmptyReader with ' + label); 187 188 test(() => { 189 190 const reader = factory().reader; 191 192 assert_true('closed' in reader, 'has a closed property'); 193 assert_equals(typeof reader.closed.then, 'function', 'closed property is thenable'); 194 195 assert_equals(typeof reader.cancel, 'function', 'has a cancel method'); 196 assert_equals(typeof reader.read, 'function', 'has a read method'); 197 assert_equals(typeof reader.releaseLock, 'function', 'has a releaseLock method'); 198 199 }, label + ': instances have the correct methods and properties'); 200 201 test(() => { 202 203 const { stream } = factory(); 204 205 assert_true(stream.locked, 'locked getter should return true'); 206 207 }, label + ': locked should be true'); 208 209 promise_test(t => { 210 211 const { read } = factory(); 212 213 read().then( 214 t.unreached_func('read() should not fulfill'), 215 t.unreached_func('read() should not reject') 216 ); 217 218 return delay(500); 219 220 }, label + ': read() should never settle'); 221 222 promise_test(t => { 223 224 const { read } = factory(); 225 226 read().then( 227 t.unreached_func('read() should not fulfill'), 228 t.unreached_func('read() should not reject') 229 ); 230 231 read().then( 232 t.unreached_func('read() should not fulfill'), 233 t.unreached_func('read() should not reject') 234 ); 235 236 return delay(500); 237 238 }, label + ': two read()s should both never settle'); 239 240 test(() => { 241 242 const { read } = factory(); 243 assert_not_equals(read(), read(), 'the promises returned should be distinct'); 244 245 }, label + ': read() should return distinct promises each time'); 246 247 test(() => { 248 249 const { stream } = factory(); 250 assert_throws_js(TypeError, () => stream.getReader(), 'stream.getReader() should throw a TypeError'); 251 252 }, label + ': getReader() again on the stream should fail'); 253 254 promise_test(async t => { 255 256 const { stream, reader, read } = factory(); 257 258 const read1 = read(); 259 const read2 = read(); 260 const closed = reader.closed; 261 262 reader.releaseLock(); 263 264 assert_false(stream.locked, 'the stream should be unlocked'); 265 266 await Promise.all([ 267 promise_rejects_js(t, TypeError, read1, 'first read should reject'), 268 promise_rejects_js(t, TypeError, read2, 'second read should reject'), 269 promise_rejects_js(t, TypeError, closed, 'closed should reject') 270 ]); 271 272 }, label + ': releasing the lock should reject all pending read requests'); 273 274 promise_test(t => { 275 276 const { reader, read } = factory(); 277 reader.releaseLock(); 278 279 return Promise.all([ 280 promise_rejects_js(t, TypeError, read()), 281 promise_rejects_js(t, TypeError, read()) 282 ]); 283 284 }, label + ': releasing the lock should cause further read() calls to reject with a TypeError'); 285 286 promise_test(t => { 287 288 const { reader } = factory(); 289 290 const closedBefore = reader.closed; 291 reader.releaseLock(); 292 const closedAfter = reader.closed; 293 294 assert_equals(closedBefore, closedAfter, 'the closed promise should not change identity'); 295 296 return promise_rejects_js(t, TypeError, closedBefore); 297 298 }, label + ': releasing the lock should cause closed calls to reject with a TypeError'); 299 300 test(() => { 301 302 const { stream, reader } = factory(); 303 304 reader.releaseLock(); 305 assert_false(stream.locked, 'locked getter should return false'); 306 307 }, label + ': releasing the lock should cause locked to become false'); 308 309 promise_test(() => { 310 311 const { reader, read } = factory(); 312 reader.cancel(); 313 314 return read().then(r => { 315 assert_object_equals(r, { value: undefined, done: true }, 'read()ing from the reader should give a done result'); 316 }); 317 318 }, label + ': canceling via the reader should cause the reader to act closed'); 319 320 promise_test(t => { 321 322 const { stream } = factory(); 323 return promise_rejects_js(t, TypeError, stream.cancel()); 324 325 }, label + ': canceling via the stream should fail'); 326 }; 327 328 self.templatedRSClosedReader = (label, factory) => { 329 test(() => {}, 'Running templatedRSClosedReader with ' + label); 330 331 promise_test(() => { 332 333 const reader = factory().reader; 334 335 return reader.read().then(v => { 336 assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly'); 337 }); 338 339 }, label + ': read() should fulfill with { value: undefined, done: true }'); 340 341 promise_test(() => { 342 343 const reader = factory().reader; 344 345 return Promise.all([ 346 reader.read().then(v => { 347 assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly'); 348 }), 349 reader.read().then(v => { 350 assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly'); 351 }) 352 ]); 353 354 }, label + ': read() multiple times should fulfill with { value: undefined, done: true }'); 355 356 promise_test(() => { 357 358 const reader = factory().reader; 359 360 return reader.read().then(() => reader.read()).then(v => { 361 assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly'); 362 }); 363 364 }, label + ': read() should work when used within another read() fulfill callback'); 365 366 promise_test(() => { 367 368 const reader = factory().reader; 369 370 return reader.closed.then(v => assert_equals(v, undefined, 'reader closed should fulfill with undefined')); 371 372 }, label + ': closed should fulfill with undefined'); 373 374 promise_test(t => { 375 376 const reader = factory().reader; 377 378 const closedBefore = reader.closed; 379 reader.releaseLock(); 380 const closedAfter = reader.closed; 381 382 assert_not_equals(closedBefore, closedAfter, 'the closed promise should change identity'); 383 384 return Promise.all([ 385 closedBefore.then(v => assert_equals(v, undefined, 'reader.closed acquired before release should fulfill')), 386 promise_rejects_js(t, TypeError, closedAfter) 387 ]); 388 389 }, label + ': releasing the lock should cause closed to reject and change identity'); 390 391 promise_test(() => { 392 393 const reader = factory().reader; 394 const cancelPromise1 = reader.cancel(); 395 const cancelPromise2 = reader.cancel(); 396 const closedReaderPromise = reader.closed; 397 398 assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises'); 399 assert_not_equals(cancelPromise1, closedReaderPromise, 'cancel() promise 1 should be distinct from reader.closed'); 400 assert_not_equals(cancelPromise2, closedReaderPromise, 'cancel() promise 2 should be distinct from reader.closed'); 401 402 return Promise.all([ 403 cancelPromise1.then(v => assert_equals(v, undefined, 'first cancel() should fulfill with undefined')), 404 cancelPromise2.then(v => assert_equals(v, undefined, 'second cancel() should fulfill with undefined')) 405 ]); 406 407 }, label + ': cancel() should return a distinct fulfilled promise each time'); 408 }; 409 410 self.templatedRSErroredReader = (label, factory, error) => { 411 test(() => {}, 'Running templatedRSErroredReader with ' + label); 412 413 promise_test(t => { 414 415 const reader = factory().reader; 416 return promise_rejects_exactly(t, error, reader.closed); 417 418 }, label + ': closed should reject with the error'); 419 420 promise_test(t => { 421 422 const reader = factory().reader; 423 const closedBefore = reader.closed; 424 425 return promise_rejects_exactly(t, error, closedBefore).then(() => { 426 reader.releaseLock(); 427 428 const closedAfter = reader.closed; 429 assert_not_equals(closedBefore, closedAfter, 'the closed promise should change identity'); 430 431 return promise_rejects_js(t, TypeError, closedAfter); 432 }); 433 434 }, label + ': releasing the lock should cause closed to reject and change identity'); 435 436 promise_test(t => { 437 438 const reader = factory().reader; 439 return promise_rejects_exactly(t, error, reader.read()); 440 441 }, label + ': read() should reject with the error'); 442 }; 443 444 self.templatedRSTwoChunksOpenReader = (label, factory, chunks) => { 445 test(() => {}, 'Running templatedRSTwoChunksOpenReader with ' + label); 446 447 promise_test(() => { 448 449 const reader = factory().reader; 450 451 return Promise.all([ 452 reader.read().then(r => { 453 assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct'); 454 }), 455 reader.read().then(r => { 456 assert_object_equals(r, { value: chunks[1], done: false }, 'second result should be correct'); 457 }) 458 ]); 459 460 }, label + ': calling read() twice without waiting will eventually give both chunks (sequential)'); 461 462 promise_test(() => { 463 464 const reader = factory().reader; 465 466 return reader.read().then(r => { 467 assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct'); 468 469 return reader.read().then(r2 => { 470 assert_object_equals(r2, { value: chunks[1], done: false }, 'second result should be correct'); 471 }); 472 }); 473 474 }, label + ': calling read() twice without waiting will eventually give both chunks (nested)'); 475 476 test(() => { 477 478 const reader = factory().reader; 479 assert_not_equals(reader.read(), reader.read(), 'the promises returned should be distinct'); 480 481 }, label + ': read() should return distinct promises each time'); 482 483 promise_test(() => { 484 485 const reader = factory().reader; 486 487 const promise1 = reader.closed.then(v => { 488 assert_equals(v, undefined, 'reader closed should fulfill with undefined'); 489 }); 490 491 const promise2 = reader.read().then(r => { 492 assert_object_equals(r, { value: chunks[0], done: false }, 493 'promise returned before cancellation should fulfill with a chunk'); 494 }); 495 496 reader.cancel(); 497 498 const promise3 = reader.read().then(r => { 499 assert_object_equals(r, { value: undefined, done: true }, 500 'promise returned after cancellation should fulfill with an end-of-stream signal'); 501 }); 502 503 return Promise.all([promise1, promise2, promise3]); 504 505 }, label + ': cancel() after a read() should still give that single read result'); 506 }; 507 508 self.templatedRSTwoChunksClosedReader = function (label, factory, chunks) { 509 test(() => {}, 'Running templatedRSTwoChunksClosedReader with ' + label); 510 511 promise_test(() => { 512 513 const reader = factory().reader; 514 515 return Promise.all([ 516 reader.read().then(r => { 517 assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct'); 518 }), 519 reader.read().then(r => { 520 assert_object_equals(r, { value: chunks[1], done: false }, 'second result should be correct'); 521 }), 522 reader.read().then(r => { 523 assert_object_equals(r, { value: undefined, done: true }, 'third result should be correct'); 524 }) 525 ]); 526 527 }, label + ': third read(), without waiting, should give { value: undefined, done: true } (sequential)'); 528 529 promise_test(() => { 530 531 const reader = factory().reader; 532 533 return reader.read().then(r => { 534 assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct'); 535 536 return reader.read().then(r2 => { 537 assert_object_equals(r2, { value: chunks[1], done: false }, 'second result should be correct'); 538 539 return reader.read().then(r3 => { 540 assert_object_equals(r3, { value: undefined, done: true }, 'third result should be correct'); 541 }); 542 }); 543 }); 544 545 }, label + ': third read(), without waiting, should give { value: undefined, done: true } (nested)'); 546 547 promise_test(() => { 548 549 const streamAndReader = factory(); 550 const stream = streamAndReader.stream; 551 const reader = streamAndReader.reader; 552 553 assert_true(stream.locked, 'stream should start locked'); 554 555 const promise = reader.closed.then(v => { 556 assert_equals(v, undefined, 'reader closed should fulfill with undefined'); 557 assert_true(stream.locked, 'stream should remain locked'); 558 }); 559 560 reader.read(); 561 reader.read(); 562 563 return promise; 564 565 }, label + 566 ': draining the stream via read() should cause the reader closed promise to fulfill, but locked stays true'); 567 568 promise_test(() => { 569 570 const streamAndReader = factory(); 571 const stream = streamAndReader.stream; 572 const reader = streamAndReader.reader; 573 574 const promise = reader.closed.then(() => { 575 assert_true(stream.locked, 'the stream should start locked'); 576 reader.releaseLock(); // Releasing the lock after reader closed should not throw. 577 assert_false(stream.locked, 'the stream should end unlocked'); 578 }); 579 580 reader.read(); 581 reader.read(); 582 583 return promise; 584 585 }, label + ': releasing the lock after the stream is closed should cause locked to become false'); 586 587 promise_test(t => { 588 589 const reader = factory().reader; 590 591 reader.releaseLock(); 592 593 return Promise.all([ 594 promise_rejects_js(t, TypeError, reader.read()), 595 promise_rejects_js(t, TypeError, reader.read()), 596 promise_rejects_js(t, TypeError, reader.read()) 597 ]); 598 599 }, label + ': releasing the lock should cause further read() calls to reject with a TypeError'); 600 601 promise_test(() => { 602 603 const streamAndReader = factory(); 604 const stream = streamAndReader.stream; 605 const reader = streamAndReader.reader; 606 607 const readerClosed = reader.closed; 608 609 assert_equals(reader.closed, readerClosed, 'accessing reader.closed twice in succession gives the same value'); 610 611 const promise = reader.read().then(() => { 612 assert_equals(reader.closed, readerClosed, 'reader.closed is the same after read() fulfills'); 613 614 reader.releaseLock(); 615 616 assert_equals(reader.closed, readerClosed, 'reader.closed is the same after releasing the lock'); 617 618 const newReader = stream.getReader(); 619 return newReader.read(); 620 }); 621 622 assert_equals(reader.closed, readerClosed, 'reader.closed is the same after calling read()'); 623 624 return promise; 625 626 }, label + ': reader\'s closed property always returns the same promise'); 627 }; 628 629 self.templatedRSTeeCancel = (label, factory) => { 630 test(() => {}, `Running templatedRSTeeCancel with ${label}`); 631 632 promise_test(async () => { 633 634 const reason1 = new Error('We\'re wanted men.'); 635 const reason2 = new Error('I have the death sentence on twelve systems.'); 636 637 let resolve; 638 const promise = new Promise(r => resolve = r); 639 const rs = factory({ 640 cancel(reason) { 641 assert_array_equals(reason, [reason1, reason2], 642 'the cancel reason should be an array containing those from the branches'); 643 resolve(); 644 } 645 }); 646 647 const [branch1, branch2] = rs.tee(); 648 await Promise.all([ 649 branch1.cancel(reason1), 650 branch2.cancel(reason2), 651 promise 652 ]); 653 654 }, `${label}: canceling both branches should aggregate the cancel reasons into an array`); 655 656 promise_test(async () => { 657 658 const reason1 = new Error('This little one\'s not worth the effort.'); 659 const reason2 = new Error('Come, let me get you something.'); 660 661 let resolve; 662 const promise = new Promise(r => resolve = r); 663 const rs = factory({ 664 cancel(reason) { 665 assert_array_equals(reason, [reason1, reason2], 666 'the cancel reason should be an array containing those from the branches'); 667 resolve(); 668 } 669 }); 670 671 const [branch1, branch2] = rs.tee(); 672 await Promise.all([ 673 branch2.cancel(reason2), 674 branch1.cancel(reason1), 675 promise 676 ]); 677 678 }, `${label}: canceling both branches in reverse order should aggregate the cancel reasons into an array`); 679 680 promise_test(async t => { 681 682 const theError = { name: 'I\'ll be careful.' }; 683 const rs = factory({ 684 cancel() { 685 throw theError; 686 } 687 }); 688 689 const [branch1, branch2] = rs.tee(); 690 await Promise.all([ 691 promise_rejects_exactly(t, theError, branch1.cancel()), 692 promise_rejects_exactly(t, theError, branch2.cancel()) 693 ]); 694 695 }, `${label}: failing to cancel the original stream should cause cancel() to reject on branches`); 696 697 promise_test(async t => { 698 699 const theError = { name: 'You just watch yourself!' }; 700 let controller; 701 const stream = factory({ 702 start(c) { 703 controller = c; 704 } 705 }); 706 707 const [branch1, branch2] = stream.tee(); 708 controller.error(theError); 709 710 await Promise.all([ 711 promise_rejects_exactly(t, theError, branch1.cancel()), 712 promise_rejects_exactly(t, theError, branch2.cancel()) 713 ]); 714 715 }, `${label}: erroring a teed stream should properly handle canceled branches`); 716 717 }; 718 719 self.templatedRSThrowAfterCloseOrError = (label, factory) => { 720 test(() => {}, 'Running templatedRSThrowAfterCloseOrError with ' + label); 721 722 const theError = new Error('a unique string'); 723 724 promise_test(async t => { 725 let controller; 726 const stream = factory({ 727 start: t.step_func((c) => { 728 controller = c; 729 }) 730 }); 731 732 controller.close(); 733 734 assert_throws_js(TypeError, () => controller.enqueue(new Uint8Array([1]))); 735 }, `${label}: enqueue() throws after close()`); 736 737 promise_test(async t => { 738 let controller; 739 const stream = factory({ 740 start: t.step_func((c) => { 741 controller = c; 742 }) 743 }); 744 745 controller.enqueue(new Uint8Array([1])); 746 controller.close(); 747 748 assert_throws_js(TypeError, () => controller.enqueue(new Uint8Array([2]))); 749 }, `${label}: enqueue() throws after enqueue() and close()`); 750 751 promise_test(async t => { 752 let controller; 753 const stream = factory({ 754 start: t.step_func((c) => { 755 controller = c; 756 }) 757 }); 758 759 controller.error(theError); 760 761 assert_throws_js(TypeError, () => controller.enqueue(new Uint8Array([1]))); 762 }, `${label}: enqueue() throws after error()`); 763 764 promise_test(async t => { 765 let controller; 766 const stream = factory({ 767 start: t.step_func((c) => { 768 controller = c; 769 }) 770 }); 771 772 controller.error(theError); 773 774 assert_throws_js(TypeError, () => controller.close()); 775 }, `${label}: close() throws after error()`); 776 };