OLD | NEW |
(Empty) | |
| 1 'use strict'; |
| 2 |
| 3 if (self.importScripts) { |
| 4 self.importScripts('../resources/test-utils.js'); |
| 5 self.importScripts('../resources/rs-utils.js'); |
| 6 self.importScripts('/resources/testharness.js'); |
| 7 } |
| 8 |
| 9 test(() => { |
| 10 |
| 11 new ReadableStream(); // ReadableStream constructed with no parameters |
| 12 new ReadableStream({ }); // ReadableStream constructed with an empty object as
parameter |
| 13 new ReadableStream({ type: undefined }); // ReadableStream constructed with un
defined type |
| 14 new ReadableStream(undefined); // ReadableStream constructed with undefined as
parameter |
| 15 |
| 16 let x; |
| 17 new ReadableStream(x); // ReadableStream constructed with an undefined variabl
e as parameter |
| 18 |
| 19 }, 'ReadableStream can be constructed with no errors'); |
| 20 |
| 21 test(() => { |
| 22 |
| 23 assert_throws(new TypeError(), () => new ReadableStream(null), 'constructor sh
ould throw when the source is null'); |
| 24 |
| 25 }, 'ReadableStream can\'t be constructed with garbage'); |
| 26 |
| 27 test(() => { |
| 28 |
| 29 assert_throws(new RangeError(), () => new ReadableStream({ type: null }), |
| 30 'constructor should throw when the type is null'); |
| 31 assert_throws(new RangeError(), () => new ReadableStream({ type: '' }), |
| 32 'constructor should throw when the type is empty string'); |
| 33 assert_throws(new RangeError(), () => new ReadableStream({ type: 'asdf' }), |
| 34 'constructor should throw when the type is asdf'); |
| 35 |
| 36 }, 'ReadableStream can\'t be constructed with an invalid type'); |
| 37 |
| 38 test(() => { |
| 39 |
| 40 const methods = ['cancel', 'constructor', 'getReader', 'pipeThrough', 'pipeTo'
, 'tee']; |
| 41 const properties = methods.concat(['locked']).sort(); |
| 42 |
| 43 const rs = new ReadableStream(); |
| 44 const proto = Object.getPrototypeOf(rs); |
| 45 |
| 46 assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties, 'sho
uld have all the correct methods'); |
| 47 |
| 48 for (const m of methods) { |
| 49 const propDesc = Object.getOwnPropertyDescriptor(proto, m); |
| 50 assert_false(propDesc.enumerable, 'method should be non-enumerable'); |
| 51 assert_true(propDesc.configurable, 'method should be configurable'); |
| 52 assert_true(propDesc.writable, 'method should be writable'); |
| 53 assert_equals(typeof rs[m], 'function', 'method should be a function'); |
| 54 } |
| 55 |
| 56 const lockedPropDesc = Object.getOwnPropertyDescriptor(proto, 'locked'); |
| 57 assert_false(lockedPropDesc.enumerable, 'locked should be non-enumerable'); |
| 58 assert_equals(lockedPropDesc.writable, undefined, 'locked should not be a data
property'); |
| 59 assert_equals(typeof lockedPropDesc.get, 'function', 'locked should have a get
ter'); |
| 60 assert_equals(lockedPropDesc.set, undefined, 'locked should not have a setter'
); |
| 61 assert_true(lockedPropDesc.configurable, 'locked should be configurable'); |
| 62 |
| 63 assert_equals(rs.cancel.length, 1, 'cancel should have 1 parameter'); |
| 64 assert_equals(rs.constructor.length, 0, 'constructor should have no parameters
'); |
| 65 assert_equals(rs.getReader.length, 0, 'getReader should have no parameters'); |
| 66 assert_equals(rs.pipeThrough.length, 2, 'pipeThrough should have 2 parameters'
); |
| 67 assert_equals(rs.pipeTo.length, 1, 'pipeTo should have 1 parameter'); |
| 68 assert_equals(rs.tee.length, 0, 'tee should have no parameters'); |
| 69 |
| 70 }, 'ReadableStream instances should have the correct list of properties'); |
| 71 |
| 72 test(() => { |
| 73 |
| 74 assert_throws(new TypeError(), () => { |
| 75 new ReadableStream({ start: 'potato' }); |
| 76 }, 'constructor should throw when start is not a function'); |
| 77 |
| 78 }, 'ReadableStream constructor should throw for non-function start arguments'); |
| 79 |
| 80 test(() => { |
| 81 |
| 82 new ReadableStream({ cancel: '2' }); |
| 83 |
| 84 }, 'ReadableStream constructor can get initial garbage as cancel argument'); |
| 85 |
| 86 test(() => { |
| 87 |
| 88 new ReadableStream({ pull: { } }); |
| 89 |
| 90 }, 'ReadableStream constructor can get initial garbage as pull argument'); |
| 91 |
| 92 test(() => { |
| 93 |
| 94 let startCalled = false; |
| 95 |
| 96 const source = { |
| 97 start(controller) { |
| 98 assert_equals(this, source, 'source is this during start'); |
| 99 |
| 100 const methods = ['close', 'enqueue', 'error', 'constructor']; |
| 101 const properties = ['desiredSize'].concat(methods).sort(); |
| 102 const proto = Object.getPrototypeOf(controller); |
| 103 |
| 104 assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties, |
| 105 'the controller should have the right properties'); |
| 106 |
| 107 for (const m of methods) { |
| 108 const propDesc = Object.getOwnPropertyDescriptor(proto, m); |
| 109 assert_equals(typeof controller[m], 'function', `should have a ${m} meth
od`); |
| 110 assert_false(propDesc.enumerable, m + ' should be non-enumerable'); |
| 111 assert_true(propDesc.configurable, m + ' should be configurable'); |
| 112 assert_true(propDesc.writable, m + ' should be writable'); |
| 113 } |
| 114 |
| 115 const desiredSizePropDesc = Object.getOwnPropertyDescriptor(proto, 'desire
dSize'); |
| 116 assert_false(desiredSizePropDesc.enumerable, 'desiredSize should be non-en
umerable'); |
| 117 assert_equals(desiredSizePropDesc.writable, undefined, 'desiredSize should
not be a data property'); |
| 118 assert_equals(typeof desiredSizePropDesc.get, 'function', 'desiredSize sho
uld have a getter'); |
| 119 assert_equals(desiredSizePropDesc.set, undefined, 'desiredSize should not
have a setter'); |
| 120 assert_true(desiredSizePropDesc.configurable, 'desiredSize should be confi
gurable'); |
| 121 |
| 122 assert_equals(controller.close.length, 0, 'close should have no parameters
'); |
| 123 assert_equals(controller.constructor.length, 4, 'constructor should have 4
parameter'); |
| 124 assert_equals(controller.enqueue.length, 1, 'enqueue should have 1 paramet
er'); |
| 125 assert_equals(controller.error.length, 1, 'error should have 1 parameter')
; |
| 126 |
| 127 startCalled = true; |
| 128 } |
| 129 }; |
| 130 |
| 131 new ReadableStream(source); |
| 132 assert_true(startCalled); |
| 133 |
| 134 }, 'ReadableStream start should be called with the proper parameters'); |
| 135 |
| 136 test(() => { |
| 137 |
| 138 let startCalled = false; |
| 139 const source = { |
| 140 start(controller) { |
| 141 const properties = ['close', 'constructor', 'desiredSize', 'enqueue', 'err
or']; |
| 142 assert_array_equals(Object.getOwnPropertyNames(Object.getPrototypeOf(contr
oller)).sort(), properties, |
| 143 'prototype should have the right properties'); |
| 144 |
| 145 controller.test = ''; |
| 146 assert_array_equals(Object.getOwnPropertyNames(Object.getPrototypeOf(contr
oller)).sort(), properties, |
| 147 'prototype should still have the right properties'); |
| 148 assert_not_equals(Object.getOwnPropertyNames(controller).indexOf('test'),
-1, |
| 149 '"test" should be a property of the controller'); |
| 150 |
| 151 startCalled = true; |
| 152 } |
| 153 }; |
| 154 |
| 155 new ReadableStream(source); |
| 156 assert_true(startCalled); |
| 157 |
| 158 }, 'ReadableStream start controller parameter should be extensible'); |
| 159 |
| 160 promise_test(() => { |
| 161 |
| 162 function SimpleStreamSource() {} |
| 163 let resolve; |
| 164 const promise = new Promise(r => resolve = r); |
| 165 SimpleStreamSource.prototype = { |
| 166 start: resolve |
| 167 }; |
| 168 |
| 169 new ReadableStream(new SimpleStreamSource()); |
| 170 return promise; |
| 171 |
| 172 }, 'ReadableStream should be able to call start method within prototype chain of
its source'); |
| 173 |
| 174 promise_test(() => { |
| 175 |
| 176 const rs = new ReadableStream({ |
| 177 start(c) { |
| 178 return delay(5).then(() => { |
| 179 c.enqueue('a'); |
| 180 c.close(); |
| 181 }); |
| 182 } |
| 183 }); |
| 184 |
| 185 const reader = rs.getReader(); |
| 186 return reader.read().then(r => { |
| 187 assert_object_equals(r, { value: 'a', done: false }, 'value read should be t
he one enqueued'); |
| 188 return reader.closed; |
| 189 }); |
| 190 |
| 191 }, 'ReadableStream start should be able to return a promise'); |
| 192 |
| 193 promise_test(() => { |
| 194 |
| 195 const theError = new Error('rejected!'); |
| 196 const rs = new ReadableStream({ |
| 197 start() { |
| 198 return delay(1).then(() => { throw theError; }); |
| 199 } |
| 200 }); |
| 201 |
| 202 return rs.getReader().closed.then(() => { |
| 203 assert_unreached('closed promise should be rejected'); |
| 204 }, e => { |
| 205 assert_equals(e, theError, 'promise should be rejected with the same error')
; |
| 206 }); |
| 207 |
| 208 }, 'ReadableStream start should be able to return a promise and reject it'); |
| 209 |
| 210 promise_test(() => { |
| 211 |
| 212 const objects = [ |
| 213 { potato: 'Give me more!' }, |
| 214 'test', |
| 215 1 |
| 216 ]; |
| 217 |
| 218 const rs = new ReadableStream({ |
| 219 start(c) { |
| 220 for (const o of objects) { |
| 221 c.enqueue(o); |
| 222 } |
| 223 c.close(); |
| 224 } |
| 225 }); |
| 226 |
| 227 const reader = rs.getReader(); |
| 228 |
| 229 return Promise.all([reader.read(), reader.read(), reader.read(), reader.closed
]).then(r => { |
| 230 assert_object_equals(r[0], { value: objects[0], done: false }, 'value read s
hould be the one enqueued'); |
| 231 assert_object_equals(r[1], { value: objects[1], done: false }, 'value read s
hould be the one enqueued'); |
| 232 assert_object_equals(r[2], { value: objects[2], done: false }, 'value read s
hould be the one enqueued'); |
| 233 }); |
| 234 |
| 235 }, 'ReadableStream should be able to enqueue different objects.'); |
| 236 |
| 237 promise_test(() => { |
| 238 |
| 239 const error = new Error('pull failure'); |
| 240 const rs = new ReadableStream({ |
| 241 pull() { |
| 242 return Promise.reject(error); |
| 243 } |
| 244 }); |
| 245 |
| 246 const reader = rs.getReader(); |
| 247 |
| 248 let closed = false; |
| 249 let read = false; |
| 250 |
| 251 return Promise.all([ |
| 252 reader.closed.then(() => { |
| 253 assert_unreached('closed should be rejected'); |
| 254 }, e => { |
| 255 closed = true; |
| 256 assert_true(read); |
| 257 assert_equals(e, error, 'closed should be rejected with the thrown error')
; |
| 258 }), |
| 259 reader.read().then(() => { |
| 260 assert_unreached('read() should be rejected'); |
| 261 }, e => { |
| 262 read = true; |
| 263 assert_false(closed); |
| 264 assert_equals(e, error, 'read() should be rejected with the thrown error')
; |
| 265 }) |
| 266 ]); |
| 267 |
| 268 }, 'ReadableStream: if pull rejects, it should error the stream'); |
| 269 |
| 270 promise_test(() => { |
| 271 |
| 272 let pullCount = 0; |
| 273 const startPromise = Promise.resolve(); |
| 274 |
| 275 new ReadableStream({ |
| 276 start() { |
| 277 return startPromise; |
| 278 }, |
| 279 pull() { |
| 280 pullCount++; |
| 281 } |
| 282 }); |
| 283 |
| 284 return startPromise.then(() => { |
| 285 assert_equals(pullCount, 1, 'pull should be called once start finishes'); |
| 286 return delay(10); |
| 287 }).then(() => { |
| 288 assert_equals(pullCount, 1, 'pull should be called exactly once'); |
| 289 }); |
| 290 |
| 291 }, 'ReadableStream: should only call pull once upon starting the stream'); |
| 292 |
| 293 promise_test(() => { |
| 294 |
| 295 let pullCount = 0; |
| 296 const startPromise = Promise.resolve(); |
| 297 |
| 298 const rs = new ReadableStream({ |
| 299 start() { |
| 300 return startPromise; |
| 301 }, |
| 302 pull(c) { |
| 303 // Don't enqueue immediately after start. We want the stream to be empty w
hen we call .read() on it. |
| 304 if (pullCount > 0) { |
| 305 c.enqueue(pullCount); |
| 306 } |
| 307 ++pullCount; |
| 308 } |
| 309 }); |
| 310 |
| 311 return startPromise.then(() => { |
| 312 assert_equals(pullCount, 1, 'pull should be called once start finishes'); |
| 313 }).then(() => { |
| 314 const reader = rs.getReader(); |
| 315 const read = reader.read(); |
| 316 assert_equals(pullCount, 2, 'pull should be called when read is called'); |
| 317 return read; |
| 318 }).then(result => { |
| 319 assert_equals(pullCount, 3, 'pull should be called again in reaction to call
ing read'); |
| 320 assert_object_equals(result, { value: 1, done: false }, 'the result read sho
uld be the one enqueued'); |
| 321 }); |
| 322 |
| 323 }, 'ReadableStream: should call pull when trying to read from a started, empty s
tream'); |
| 324 |
| 325 promise_test(() => { |
| 326 |
| 327 let pullCount = 0; |
| 328 const startPromise = Promise.resolve(); |
| 329 |
| 330 const rs = new ReadableStream({ |
| 331 start(c) { |
| 332 c.enqueue('a'); |
| 333 return startPromise; |
| 334 }, |
| 335 pull() { |
| 336 pullCount++; |
| 337 } |
| 338 }); |
| 339 |
| 340 const read = rs.getReader().read(); |
| 341 assert_equals(pullCount, 0, 'calling read() should not cause pull to be called
yet'); |
| 342 |
| 343 return startPromise.then(() => { |
| 344 assert_equals(pullCount, 1, 'pull should be called once start finishes'); |
| 345 return read; |
| 346 }).then(r => { |
| 347 assert_object_equals(r, { value: 'a', done: false }, 'first read() should re
turn first chunk'); |
| 348 assert_equals(pullCount, 1, 'pull should not have been called again'); |
| 349 return delay(10); |
| 350 }).then(() => { |
| 351 assert_equals(pullCount, 1, 'pull should be called exactly once'); |
| 352 }); |
| 353 |
| 354 }, 'ReadableStream: should only call pull once on a non-empty stream read from b
efore start fulfills'); |
| 355 |
| 356 promise_test(() => { |
| 357 |
| 358 let pullCount = 0; |
| 359 const startPromise = Promise.resolve(); |
| 360 |
| 361 const rs = new ReadableStream({ |
| 362 start(c) { |
| 363 c.enqueue('a'); |
| 364 return startPromise; |
| 365 }, |
| 366 pull() { |
| 367 pullCount++; |
| 368 } |
| 369 }); |
| 370 |
| 371 return startPromise.then(() => { |
| 372 assert_equals(pullCount, 0, 'pull should not be called once start finishes,
since the queue is full'); |
| 373 |
| 374 const read = rs.getReader().read(); |
| 375 assert_equals(pullCount, 1, 'calling read() should cause pull to be called i
mmediately'); |
| 376 return read; |
| 377 }).then(r => { |
| 378 assert_object_equals(r, { value: 'a', done: false }, 'first read() should re
turn first chunk'); |
| 379 return delay(10); |
| 380 }).then(() => { |
| 381 assert_equals(pullCount, 1, 'pull should be called exactly once'); |
| 382 }); |
| 383 |
| 384 }, 'ReadableStream: should only call pull once on a non-empty stream read from a
fter start fulfills'); |
| 385 |
| 386 promise_test(() => { |
| 387 |
| 388 let pullCount = 0; |
| 389 let controller; |
| 390 const startPromise = Promise.resolve(); |
| 391 |
| 392 const rs = new ReadableStream({ |
| 393 start(c) { |
| 394 controller = c; |
| 395 return startPromise; |
| 396 }, |
| 397 pull() { |
| 398 ++pullCount; |
| 399 } |
| 400 }); |
| 401 |
| 402 const reader = rs.getReader(); |
| 403 return startPromise.then(() => { |
| 404 assert_equals(pullCount, 1, 'pull should have been called once by the time t
he stream starts'); |
| 405 |
| 406 controller.enqueue('a'); |
| 407 assert_equals(pullCount, 1, 'pull should not have been called again after en
queue'); |
| 408 |
| 409 return reader.read(); |
| 410 }).then(() => { |
| 411 assert_equals(pullCount, 2, 'pull should have been called again after read')
; |
| 412 |
| 413 return delay(10); |
| 414 }).then(() => { |
| 415 assert_equals(pullCount, 2, 'pull should be called exactly twice'); |
| 416 }); |
| 417 }, 'ReadableStream: should call pull in reaction to read()ing the last chunk, if
not draining'); |
| 418 |
| 419 promise_test(() => { |
| 420 |
| 421 let pullCount = 0; |
| 422 let controller; |
| 423 const startPromise = Promise.resolve(); |
| 424 |
| 425 const rs = new ReadableStream({ |
| 426 start(c) { |
| 427 controller = c; |
| 428 return startPromise; |
| 429 }, |
| 430 pull() { |
| 431 ++pullCount; |
| 432 } |
| 433 }); |
| 434 |
| 435 const reader = rs.getReader(); |
| 436 |
| 437 return startPromise.then(() => { |
| 438 assert_equals(pullCount, 1, 'pull should have been called once by the time t
he stream starts'); |
| 439 |
| 440 controller.enqueue('a'); |
| 441 assert_equals(pullCount, 1, 'pull should not have been called again after en
queue'); |
| 442 |
| 443 controller.close(); |
| 444 |
| 445 return reader.read(); |
| 446 }).then(() => { |
| 447 assert_equals(pullCount, 1, 'pull should not have been called a second time
after read'); |
| 448 |
| 449 return delay(10); |
| 450 }).then(() => { |
| 451 assert_equals(pullCount, 1, 'pull should be called exactly once'); |
| 452 }); |
| 453 |
| 454 }, 'ReadableStream: should not call pull() in reaction to read()ing the last chu
nk, if draining'); |
| 455 |
| 456 promise_test(() => { |
| 457 |
| 458 let resolve; |
| 459 let returnedPromise; |
| 460 let timesCalled = 0; |
| 461 const startPromise = Promise.resolve(); |
| 462 |
| 463 const rs = new ReadableStream({ |
| 464 start() { |
| 465 return startPromise; |
| 466 }, |
| 467 pull(c) { |
| 468 c.enqueue(++timesCalled); |
| 469 returnedPromise = new Promise(r => resolve = r); |
| 470 return returnedPromise; |
| 471 } |
| 472 }); |
| 473 const reader = rs.getReader(); |
| 474 |
| 475 return startPromise.then(() => { |
| 476 return reader.read(); |
| 477 }).then(result1 => { |
| 478 assert_equals(timesCalled, 1, |
| 479 'pull should have been called once after start, but not yet have been call
ed a second time'); |
| 480 assert_object_equals(result1, { value: 1, done: false }, 'read() should fulf
ill with the enqueued value'); |
| 481 |
| 482 return delay(10); |
| 483 }).then(() => { |
| 484 assert_equals(timesCalled, 1, 'after 10 ms, pull should still only have been
called once'); |
| 485 |
| 486 resolve(); |
| 487 return returnedPromise; |
| 488 }).then(() => { |
| 489 assert_equals(timesCalled, 2, |
| 490 'after the promise returned by pull is fulfilled, pull should be called a
second time'); |
| 491 }); |
| 492 |
| 493 }, 'ReadableStream: should not call pull until the previous pull call\'s promise
fulfills'); |
| 494 |
| 495 promise_test(() => { |
| 496 |
| 497 let timesCalled = 0; |
| 498 const startPromise = Promise.resolve(); |
| 499 |
| 500 const rs = new ReadableStream( |
| 501 { |
| 502 start(c) { |
| 503 c.enqueue('a'); |
| 504 c.enqueue('b'); |
| 505 c.enqueue('c'); |
| 506 return startPromise; |
| 507 }, |
| 508 pull() { |
| 509 ++timesCalled; |
| 510 } |
| 511 }, |
| 512 { |
| 513 size() { |
| 514 return 1; |
| 515 }, |
| 516 highWaterMark: Infinity |
| 517 } |
| 518 ); |
| 519 const reader = rs.getReader(); |
| 520 |
| 521 return startPromise.then(() => { |
| 522 return reader.read(); |
| 523 }).then(result1 => { |
| 524 assert_object_equals(result1, { value: 'a', done: false }, 'first chunk shou
ld be as expected'); |
| 525 |
| 526 return reader.read(); |
| 527 }).then(result2 => { |
| 528 assert_object_equals(result2, { value: 'b', done: false }, 'second chunk sho
uld be as expected'); |
| 529 |
| 530 return reader.read(); |
| 531 }).then(result3 => { |
| 532 assert_object_equals(result3, { value: 'c', done: false }, 'third chunk shou
ld be as expected'); |
| 533 |
| 534 return delay(10); |
| 535 }).then(() => { |
| 536 // Once for after start, and once for every read. |
| 537 assert_equals(timesCalled, 4, 'pull() should be called exactly four times'); |
| 538 }); |
| 539 |
| 540 }, 'ReadableStream: should pull after start, and after every read'); |
| 541 |
| 542 promise_test(() => { |
| 543 |
| 544 let timesCalled = 0; |
| 545 const startPromise = Promise.resolve(); |
| 546 |
| 547 const rs = new ReadableStream({ |
| 548 start(c) { |
| 549 c.enqueue('a'); |
| 550 c.close(); |
| 551 return startPromise; |
| 552 }, |
| 553 pull() { |
| 554 ++timesCalled; |
| 555 } |
| 556 }); |
| 557 |
| 558 const reader = rs.getReader(); |
| 559 return startPromise.then(() => { |
| 560 assert_equals(timesCalled, 0, 'after start finishes, pull should not have be
en called'); |
| 561 |
| 562 return reader.read(); |
| 563 }).then(() => { |
| 564 assert_equals(timesCalled, 0, 'reading should not have triggered a pull call
'); |
| 565 |
| 566 return reader.closed; |
| 567 }).then(() => { |
| 568 assert_equals(timesCalled, 0, 'stream should have closed with still no calls
to pull'); |
| 569 }); |
| 570 |
| 571 }, 'ReadableStream: should not call pull after start if the stream is now closed
'); |
| 572 |
| 573 promise_test(() => { |
| 574 |
| 575 let timesCalled = 0; |
| 576 let resolve; |
| 577 const ready = new Promise(r => resolve = r); |
| 578 |
| 579 new ReadableStream( |
| 580 { |
| 581 start() {}, |
| 582 pull(c) { |
| 583 c.enqueue(++timesCalled); |
| 584 |
| 585 if (timesCalled === 4) { |
| 586 resolve(); |
| 587 } |
| 588 } |
| 589 }, |
| 590 { |
| 591 size() { |
| 592 return 1; |
| 593 }, |
| 594 highWaterMark: 4 |
| 595 } |
| 596 ); |
| 597 |
| 598 return ready.then(() => { |
| 599 // after start: size = 0, pull() |
| 600 // after enqueue(1): size = 1, pull() |
| 601 // after enqueue(2): size = 2, pull() |
| 602 // after enqueue(3): size = 3, pull() |
| 603 // after enqueue(4): size = 4, do not pull |
| 604 assert_equals(timesCalled, 4, 'pull() should have been called four times'); |
| 605 }); |
| 606 |
| 607 }, 'ReadableStream: should call pull after enqueueing from inside pull (with no
read requests), if strategy allows'); |
| 608 |
| 609 promise_test(() => { |
| 610 |
| 611 let pullCalled = false; |
| 612 |
| 613 const rs = new ReadableStream({ |
| 614 pull(c) { |
| 615 pullCalled = true; |
| 616 c.close(); |
| 617 } |
| 618 }); |
| 619 |
| 620 const reader = rs.getReader(); |
| 621 return reader.closed.then(() => { |
| 622 assert_true(pullCalled); |
| 623 }); |
| 624 |
| 625 }, 'ReadableStream pull should be able to close a stream.'); |
| 626 |
| 627 promise_test(t => { |
| 628 |
| 629 const controllerError = { name: 'controller error' }; |
| 630 |
| 631 const rs = new ReadableStream({ |
| 632 pull(c) { |
| 633 c.error(controllerError); |
| 634 } |
| 635 }); |
| 636 |
| 637 return promise_rejects(t, controllerError, rs.getReader().closed); |
| 638 |
| 639 }, 'ReadableStream pull should be able to error a stream.'); |
| 640 |
| 641 promise_test(t => { |
| 642 |
| 643 const controllerError = { name: 'controller error' }; |
| 644 const thrownError = { name: 'thrown error' }; |
| 645 |
| 646 const rs = new ReadableStream({ |
| 647 pull(c) { |
| 648 c.error(controllerError); |
| 649 throw thrownError; |
| 650 } |
| 651 }); |
| 652 |
| 653 return promise_rejects(t, controllerError, rs.getReader().closed); |
| 654 |
| 655 }, 'ReadableStream pull should be able to error a stream and throw.'); |
| 656 |
| 657 test(() => { |
| 658 |
| 659 let startCalled = false; |
| 660 |
| 661 new ReadableStream({ |
| 662 start(c) { |
| 663 assert_equals(c.enqueue('a'), undefined, 'the first enqueue should return
undefined'); |
| 664 c.close(); |
| 665 |
| 666 assert_throws(new TypeError(), () => c.enqueue('b'), 'enqueue after close
should throw a TypeError'); |
| 667 startCalled = true; |
| 668 } |
| 669 }); |
| 670 |
| 671 assert_true(startCalled); |
| 672 |
| 673 }, 'ReadableStream: enqueue should throw when the stream is readable but drainin
g'); |
| 674 |
| 675 test(() => { |
| 676 |
| 677 let startCalled = false; |
| 678 |
| 679 new ReadableStream({ |
| 680 start(c) { |
| 681 c.close(); |
| 682 |
| 683 assert_throws(new TypeError(), () => c.enqueue('a'), 'enqueue after close
should throw a TypeError'); |
| 684 startCalled = true; |
| 685 } |
| 686 }); |
| 687 |
| 688 assert_true(startCalled); |
| 689 |
| 690 }, 'ReadableStream: enqueue should throw when the stream is closed'); |
| 691 |
| 692 promise_test(() => { |
| 693 |
| 694 let startCalled = 0; |
| 695 let pullCalled = 0; |
| 696 let cancelCalled = 0; |
| 697 |
| 698 /* eslint-disable no-use-before-define */ |
| 699 class Source { |
| 700 start(c) { |
| 701 startCalled++; |
| 702 assert_equals(this, theSource, 'start() should be called with the correct
this'); |
| 703 c.enqueue('a'); |
| 704 } |
| 705 |
| 706 pull() { |
| 707 pullCalled++; |
| 708 assert_equals(this, theSource, 'pull() should be called with the correct t
his'); |
| 709 } |
| 710 |
| 711 cancel() { |
| 712 cancelCalled++; |
| 713 assert_equals(this, theSource, 'cancel() should be called with the correct
this'); |
| 714 } |
| 715 } |
| 716 /* eslint-enable no-use-before-define */ |
| 717 |
| 718 const theSource = new Source(); |
| 719 theSource.debugName = 'the source object passed to the constructor'; // makes
test failures easier to diagnose |
| 720 |
| 721 const rs = new ReadableStream(theSource); |
| 722 const reader = rs.getReader(); |
| 723 |
| 724 return reader.read().then(() => { |
| 725 reader.releaseLock(); |
| 726 rs.cancel(); |
| 727 assert_equals(startCalled, 1); |
| 728 assert_equals(pullCalled, 1); |
| 729 assert_equals(cancelCalled, 1); |
| 730 return rs.getReader().closed; |
| 731 }); |
| 732 |
| 733 }, 'ReadableStream: should call underlying source methods as methods'); |
| 734 |
| 735 test(() => { |
| 736 |
| 737 let startCalled = false; |
| 738 new ReadableStream({ |
| 739 start(c) { |
| 740 assert_equals(c.desiredSize, 1); |
| 741 c.enqueue('a'); |
| 742 assert_equals(c.desiredSize, 0); |
| 743 c.enqueue('b'); |
| 744 assert_equals(c.desiredSize, -1); |
| 745 c.enqueue('c'); |
| 746 assert_equals(c.desiredSize, -2); |
| 747 c.enqueue('d'); |
| 748 assert_equals(c.desiredSize, -3); |
| 749 c.enqueue('e'); |
| 750 startCalled = true; |
| 751 } |
| 752 }); |
| 753 |
| 754 assert_true(startCalled); |
| 755 |
| 756 }, 'ReadableStream strategies: the default strategy should give desiredSize of 1
to start, decreasing by 1 per enqueue'); |
| 757 |
| 758 promise_test(() => { |
| 759 |
| 760 let controller; |
| 761 const rs = new ReadableStream({ |
| 762 start(c) { |
| 763 controller = c; |
| 764 } |
| 765 }); |
| 766 const reader = rs.getReader(); |
| 767 |
| 768 assert_equals(controller.desiredSize, 1, 'desiredSize should start at 1'); |
| 769 controller.enqueue('a'); |
| 770 assert_equals(controller.desiredSize, 0, 'desiredSize should decrease to 0 aft
er first enqueue'); |
| 771 |
| 772 return reader.read().then(result1 => { |
| 773 assert_object_equals(result1, { value: 'a', done: false }, 'first chunk read
should be correct'); |
| 774 |
| 775 assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 afte
r the first read'); |
| 776 controller.enqueue('b'); |
| 777 assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 af
ter the second enqueue'); |
| 778 |
| 779 return reader.read(); |
| 780 }).then(result2 => { |
| 781 assert_object_equals(result2, { value: 'b', done: false }, 'second chunk rea
d should be correct'); |
| 782 |
| 783 assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 afte
r the second read'); |
| 784 controller.enqueue('c'); |
| 785 assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 af
ter the third enqueue'); |
| 786 |
| 787 return reader.read(); |
| 788 }).then(result3 => { |
| 789 assert_object_equals(result3, { value: 'c', done: false }, 'third chunk read
should be correct'); |
| 790 |
| 791 assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 afte
r the third read'); |
| 792 controller.enqueue('d'); |
| 793 assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 af
ter the fourth enqueue'); |
| 794 }); |
| 795 |
| 796 }, 'ReadableStream strategies: the default strategy should continue giving desir
edSize of 1 if the chunks are read immediately'); |
| 797 |
| 798 promise_test(t => { |
| 799 |
| 800 const randomSource = new RandomPushSource(8); |
| 801 |
| 802 const rs = new ReadableStream({ |
| 803 start(c) { |
| 804 assert_equals(typeof c, 'object', 'c should be an object in start'); |
| 805 assert_equals(typeof c.enqueue, 'function', 'enqueue should be a function
in start'); |
| 806 assert_equals(typeof c.close, 'function', 'close should be a function in s
tart'); |
| 807 assert_equals(typeof c.error, 'function', 'error should be a function in s
tart'); |
| 808 |
| 809 randomSource.ondata = t.step_func(chunk => { |
| 810 if (!c.enqueue(chunk) <= 0) { |
| 811 randomSource.readStop(); |
| 812 } |
| 813 }); |
| 814 |
| 815 randomSource.onend = c.close.bind(c); |
| 816 randomSource.onerror = c.error.bind(c); |
| 817 }, |
| 818 |
| 819 pull(c) { |
| 820 assert_equals(typeof c, 'object', 'c should be an object in pull'); |
| 821 assert_equals(typeof c.enqueue, 'function', 'enqueue should be a function
in pull'); |
| 822 assert_equals(typeof c.close, 'function', 'close should be a function in p
ull'); |
| 823 |
| 824 randomSource.readStart(); |
| 825 } |
| 826 }); |
| 827 |
| 828 return readableStreamToArray(rs).then(chunks => { |
| 829 assert_equals(chunks.length, 8, '8 chunks should be read'); |
| 830 for (const chunk of chunks) { |
| 831 assert_equals(chunk.length, 128, 'chunk should have 128 bytes'); |
| 832 } |
| 833 }); |
| 834 |
| 835 }, 'ReadableStream integration test: adapting a random push source'); |
| 836 |
| 837 promise_test(() => { |
| 838 |
| 839 const rs = sequentialReadableStream(10); |
| 840 |
| 841 return readableStreamToArray(rs).then(chunks => { |
| 842 assert_true(rs.source.closed, 'source should be closed after all chunks are
read'); |
| 843 assert_array_equals(chunks, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'the expected 1
0 chunks should be read'); |
| 844 }); |
| 845 |
| 846 }, 'ReadableStream integration test: adapting a sync pull source'); |
| 847 |
| 848 promise_test(() => { |
| 849 |
| 850 const rs = sequentialReadableStream(10, { async: true }); |
| 851 |
| 852 return readableStreamToArray(rs).then(chunks => { |
| 853 assert_true(rs.source.closed, 'source should be closed after all chunks are
read'); |
| 854 assert_array_equals(chunks, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'the expected 1
0 chunks should be read'); |
| 855 }); |
| 856 |
| 857 }, 'ReadableStream integration test: adapting an async pull source'); |
| 858 |
| 859 done(); |
OLD | NEW |