OLD | NEW |
(Empty) | |
| 1 'use strict'; |
| 2 |
| 3 if (self.importScripts) { |
| 4 self.importScripts('../resources/rs-utils.js'); |
| 5 self.importScripts('/resources/testharness.js'); |
| 6 } |
| 7 |
| 8 let ReadableStreamReader; |
| 9 |
| 10 test(() => { |
| 11 |
| 12 // It's not exposed globally, but we test a few of its properties here. |
| 13 ReadableStreamReader = (new ReadableStream()).getReader().constructor; |
| 14 |
| 15 }, 'Can get the ReadableStreamReader constructor indirectly'); |
| 16 |
| 17 test(() => { |
| 18 |
| 19 assert_throws(new TypeError(), () => new ReadableStreamReader('potato')); |
| 20 assert_throws(new TypeError(), () => new ReadableStreamReader({})); |
| 21 assert_throws(new TypeError(), () => new ReadableStreamReader()); |
| 22 |
| 23 }, 'ReadableStreamReader constructor should get a ReadableStream object as argum
ent'); |
| 24 |
| 25 test(() => { |
| 26 |
| 27 const methods = ['cancel', 'constructor', 'read', 'releaseLock']; |
| 28 const properties = methods.concat(['closed']).sort(); |
| 29 |
| 30 const rsReader = new ReadableStreamReader(new ReadableStream()); |
| 31 const proto = Object.getPrototypeOf(rsReader); |
| 32 |
| 33 assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties); |
| 34 |
| 35 for (const m of methods) { |
| 36 const propDesc = Object.getOwnPropertyDescriptor(proto, m); |
| 37 assert_equals(propDesc.enumerable, false, 'method should be non-enumerable')
; |
| 38 assert_equals(propDesc.configurable, true, 'method should be configurable'); |
| 39 assert_equals(propDesc.writable, true, 'method should be writable'); |
| 40 assert_equals(typeof rsReader[m], 'function', 'should have be a method'); |
| 41 } |
| 42 |
| 43 const closedPropDesc = Object.getOwnPropertyDescriptor(proto, 'closed'); |
| 44 assert_equals(closedPropDesc.enumerable, false, 'closed should be non-enumerab
le'); |
| 45 assert_equals(closedPropDesc.configurable, true, 'closed should be configurabl
e'); |
| 46 assert_not_equals(closedPropDesc.get, undefined, 'closed should have a getter'
); |
| 47 assert_equals(closedPropDesc.set, undefined, 'closed should not have a setter'
); |
| 48 |
| 49 assert_equals(rsReader.cancel.length, 1, 'cancel has 1 parameter'); |
| 50 assert_not_equals(rsReader.closed, undefined, 'has a non-undefined closed prop
erty'); |
| 51 assert_equals(typeof rsReader.closed.then, 'function', 'closed property is the
nable'); |
| 52 assert_equals(typeof rsReader.constructor, 'function', 'has a constructor meth
od'); |
| 53 assert_equals(rsReader.constructor.length, 1, 'constructor has 1 parameter'); |
| 54 assert_equals(typeof rsReader.read, 'function', 'has a getReader method'); |
| 55 assert_equals(rsReader.read.length, 0, 'read has no parameters'); |
| 56 assert_equals(typeof rsReader.releaseLock, 'function', 'has a releaseLock meth
od'); |
| 57 assert_equals(rsReader.releaseLock.length, 0, 'releaseLock has no parameters')
; |
| 58 |
| 59 }, 'ReadableStreamReader instances should have the correct list of properties'); |
| 60 |
| 61 test(() => { |
| 62 |
| 63 const rsReader = new ReadableStreamReader(new ReadableStream()); |
| 64 assert_equals(rsReader.closed, rsReader.closed, 'closed should return the same
promise'); |
| 65 |
| 66 }, 'ReadableStreamReader closed should always return the same promise object'); |
| 67 |
| 68 test(() => { |
| 69 |
| 70 const rs = new ReadableStream(); |
| 71 new ReadableStreamReader(rs); // Constructing directly the first time should b
e fine. |
| 72 assert_throws(new TypeError(), () => new ReadableStreamReader(rs), |
| 73 'constructing directly the second time should fail'); |
| 74 |
| 75 }, 'Constructing a ReadableStreamReader directly should fail if the stream is al
ready locked (via direct ' + |
| 76 'construction)'); |
| 77 |
| 78 test(() => { |
| 79 |
| 80 const rs = new ReadableStream(); |
| 81 new ReadableStreamReader(rs); // Constructing directly should be fine. |
| 82 assert_throws(new TypeError(), () => rs.getReader(), 'getReader() should fail'
); |
| 83 |
| 84 }, 'Getting a ReadableStreamReader via getReader should fail if the stream is al
ready locked (via direct ' + |
| 85 'construction)'); |
| 86 |
| 87 test(() => { |
| 88 |
| 89 const rs = new ReadableStream(); |
| 90 rs.getReader(); // getReader() should be fine. |
| 91 assert_throws(new TypeError(), () => new ReadableStreamReader(rs), 'constructi
ng directly should fail'); |
| 92 |
| 93 }, 'Constructing a ReadableStreamReader directly should fail if the stream is al
ready locked (via getReader)'); |
| 94 |
| 95 test(() => { |
| 96 |
| 97 const rs = new ReadableStream(); |
| 98 rs.getReader(); // getReader() should be fine. |
| 99 assert_throws(new TypeError(), () => rs.getReader(), 'getReader() should fail'
); |
| 100 |
| 101 }, 'Getting a ReadableStreamReader via getReader should fail if the stream is al
ready locked (via getReader)'); |
| 102 |
| 103 test(() => { |
| 104 |
| 105 const rs = new ReadableStream({ |
| 106 start(c) { |
| 107 c.close(); |
| 108 } |
| 109 }); |
| 110 |
| 111 new ReadableStreamReader(rs); // Constructing directly should not throw. |
| 112 |
| 113 }, 'Constructing a ReadableStreamReader directly should be OK if the stream is c
losed'); |
| 114 |
| 115 test(() => { |
| 116 |
| 117 const theError = new Error('don\'t say i didn\'t warn ya'); |
| 118 const rs = new ReadableStream({ |
| 119 start(c) { |
| 120 c.error(theError); |
| 121 } |
| 122 }); |
| 123 |
| 124 new ReadableStreamReader(rs); // Constructing directly should not throw. |
| 125 |
| 126 }, 'Constructing a ReadableStreamReader directly should be OK if the stream is e
rrored'); |
| 127 |
| 128 promise_test(() => { |
| 129 |
| 130 let controller; |
| 131 const rs = new ReadableStream({ |
| 132 start(c) { |
| 133 controller = c; |
| 134 } |
| 135 }); |
| 136 const reader = rs.getReader(); |
| 137 |
| 138 const promise = reader.read().then(result => { |
| 139 assert_object_equals(result, { value: 'a', done: false }, 'read() should ful
fill with the enqueued chunk'); |
| 140 }); |
| 141 |
| 142 controller.enqueue('a'); |
| 143 return promise; |
| 144 |
| 145 }, 'Reading from a reader for an empty stream will wait until a chunk is availab
le'); |
| 146 |
| 147 promise_test(() => { |
| 148 |
| 149 let cancelCalled = false; |
| 150 const passedReason = new Error('it wasn\'t the right time, sorry'); |
| 151 const rs = new ReadableStream({ |
| 152 cancel(reason) { |
| 153 assert_true(rs.locked, 'the stream should still be locked'); |
| 154 assert_throws(new TypeError(), () => rs.getReader(), 'should not be able t
o get another reader'); |
| 155 assert_equals(reason, passedReason, 'the cancellation reason is passed thr
ough to the underlying source'); |
| 156 cancelCalled = true; |
| 157 } |
| 158 }); |
| 159 |
| 160 const reader = rs.getReader(); |
| 161 return reader.cancel(passedReason).then(() => assert_true(cancelCalled)); |
| 162 |
| 163 }, 'cancel() on a reader does not release the reader'); |
| 164 |
| 165 promise_test(() => { |
| 166 |
| 167 let controller; |
| 168 const rs = new ReadableStream({ |
| 169 start(c) { |
| 170 controller = c; |
| 171 } |
| 172 }); |
| 173 |
| 174 const reader = rs.getReader(); |
| 175 const promise = reader.closed; |
| 176 |
| 177 controller.close(); |
| 178 return promise; |
| 179 |
| 180 }, 'closed should be fulfilled after stream is closed (.closed access before acq
uiring)'); |
| 181 |
| 182 promise_test(t => { |
| 183 |
| 184 let controller; |
| 185 const rs = new ReadableStream({ |
| 186 start(c) { |
| 187 controller = c; |
| 188 } |
| 189 }); |
| 190 |
| 191 const reader1 = rs.getReader(); |
| 192 |
| 193 reader1.releaseLock(); |
| 194 |
| 195 const reader2 = rs.getReader(); |
| 196 controller.close(); |
| 197 |
| 198 return Promise.all([ |
| 199 promise_rejects(t, new TypeError(), reader1.closed), |
| 200 reader2.closed |
| 201 ]); |
| 202 |
| 203 }, 'closed should be rejected after reader releases its lock (multiple stream lo
cks)'); |
| 204 |
| 205 promise_test(() => { |
| 206 |
| 207 const rs = new ReadableStream({ |
| 208 start(c) { |
| 209 c.enqueue('a'); |
| 210 c.enqueue('b'); |
| 211 c.close(); |
| 212 } |
| 213 }); |
| 214 |
| 215 const reader1 = rs.getReader(); |
| 216 const promise1 = reader1.read().then(r => { |
| 217 assert_object_equals(r, { value: 'a', done: false }, 'reading the first chun
k from reader1 works'); |
| 218 }); |
| 219 reader1.releaseLock(); |
| 220 |
| 221 const reader2 = rs.getReader(); |
| 222 const promise2 = reader2.read().then(r => { |
| 223 assert_object_equals(r, { value: 'b', done: false }, 'reading the second chu
nk from reader2 works'); |
| 224 }); |
| 225 reader2.releaseLock(); |
| 226 |
| 227 return Promise.all([promise1, promise2]); |
| 228 |
| 229 }, 'Multiple readers can access the stream in sequence'); |
| 230 |
| 231 promise_test(() => { |
| 232 const rs = new ReadableStream({ |
| 233 start(c) { |
| 234 c.enqueue('a'); |
| 235 } |
| 236 }); |
| 237 |
| 238 const reader1 = rs.getReader(); |
| 239 reader1.releaseLock(); |
| 240 |
| 241 const reader2 = rs.getReader(); |
| 242 |
| 243 // Should be a no-op |
| 244 reader1.releaseLock(); |
| 245 |
| 246 return reader2.read().then(result => { |
| 247 assert_object_equals(result, { value: 'a', done: false }, |
| 248 'read() should still work on reader2 even after reader1
is released'); |
| 249 }); |
| 250 |
| 251 }, 'Cannot use an already-released reader to unlock a stream again'); |
| 252 |
| 253 promise_test(t => { |
| 254 |
| 255 const rs = new ReadableStream({ |
| 256 start(c) { |
| 257 c.enqueue('a'); |
| 258 }, |
| 259 cancel() { |
| 260 assert_unreached('underlying source cancel should not be called'); |
| 261 } |
| 262 }); |
| 263 |
| 264 const reader = rs.getReader(); |
| 265 reader.releaseLock(); |
| 266 const cancelPromise = reader.cancel(); |
| 267 |
| 268 const reader2 = rs.getReader(); |
| 269 const readPromise = reader2.read().then(r => { |
| 270 assert_object_equals(r, { value: 'a', done: false }, 'a new reader should be
able to read a chunk'); |
| 271 }); |
| 272 |
| 273 return Promise.all([ |
| 274 promise_rejects(t, new TypeError(), cancelPromise), |
| 275 readPromise |
| 276 ]); |
| 277 |
| 278 }, 'cancel() on a released reader is a no-op and does not pass through'); |
| 279 |
| 280 promise_test(t => { |
| 281 |
| 282 const promiseAsserts = []; |
| 283 |
| 284 let controller; |
| 285 const theError = { name: 'unique error' }; |
| 286 const rs = new ReadableStream({ |
| 287 start(c) { |
| 288 controller = c; |
| 289 } |
| 290 }); |
| 291 |
| 292 const reader1 = rs.getReader(); |
| 293 |
| 294 promiseAsserts.push( |
| 295 promise_rejects(t, theError, reader1.closed), |
| 296 promise_rejects(t, theError, reader1.read()) |
| 297 ); |
| 298 |
| 299 assert_throws(new TypeError(), () => rs.getReader(), 'trying to get another re
ader before erroring should throw'); |
| 300 |
| 301 controller.error(theError); |
| 302 |
| 303 reader1.releaseLock(); |
| 304 |
| 305 const reader2 = rs.getReader(); |
| 306 |
| 307 promiseAsserts.push( |
| 308 promise_rejects(t, theError, reader2.closed), |
| 309 promise_rejects(t, theError, reader2.read()) |
| 310 ); |
| 311 |
| 312 return Promise.all(promiseAsserts); |
| 313 |
| 314 }, 'Getting a second reader after erroring the stream and releasing the reader s
hould succeed'); |
| 315 |
| 316 promise_test(t => { |
| 317 |
| 318 let controller; |
| 319 const rs = new ReadableStream({ |
| 320 start(c) { |
| 321 controller = c; |
| 322 } |
| 323 }); |
| 324 |
| 325 const promise = rs.getReader().closed.then( |
| 326 t.unreached_func('closed promise should not be fulfilled when stream is erro
red'), |
| 327 err => { |
| 328 assert_equals(err, undefined, 'passed error should be undefined as it was'
); |
| 329 } |
| 330 ); |
| 331 |
| 332 controller.error(); |
| 333 return promise; |
| 334 |
| 335 }, 'ReadableStreamReader closed promise should be rejected with undefined if tha
t is the error'); |
| 336 |
| 337 |
| 338 promise_test(t => { |
| 339 |
| 340 const rs = new ReadableStream({ |
| 341 start() { |
| 342 return Promise.reject(); |
| 343 } |
| 344 }); |
| 345 |
| 346 return rs.getReader().read().then( |
| 347 t.unreached_func('read promise should not be fulfilled when stream is errore
d'), |
| 348 err => { |
| 349 assert_equals(err, undefined, 'passed error should be undefined as it was'
); |
| 350 } |
| 351 ); |
| 352 |
| 353 }, 'ReadableStreamReader: if start rejects with no parameter, it should error th
e stream with an undefined error'); |
| 354 |
| 355 promise_test(t => { |
| 356 |
| 357 const theError = { name: 'unique string' }; |
| 358 let controller; |
| 359 const rs = new ReadableStream({ |
| 360 start(c) { |
| 361 controller = c; |
| 362 } |
| 363 }); |
| 364 |
| 365 const promise = promise_rejects(t, theError, rs.getReader().closed); |
| 366 |
| 367 controller.error(theError); |
| 368 return promise; |
| 369 |
| 370 }, 'Erroring a ReadableStream after checking closed should reject ReadableStream
Reader closed promise'); |
| 371 |
| 372 promise_test(t => { |
| 373 |
| 374 const theError = { name: 'unique string' }; |
| 375 let controller; |
| 376 const rs = new ReadableStream({ |
| 377 start(c) { |
| 378 controller = c; |
| 379 } |
| 380 }); |
| 381 |
| 382 controller.error(theError); |
| 383 |
| 384 // Let's call getReader twice for extra test coverage of this code path. |
| 385 rs.getReader().releaseLock(); |
| 386 |
| 387 return promise_rejects(t, theError, rs.getReader().closed); |
| 388 |
| 389 }, 'Erroring a ReadableStream before checking closed should reject ReadableStrea
mReader closed promise'); |
| 390 |
| 391 promise_test(() => { |
| 392 |
| 393 let controller; |
| 394 const rs = new ReadableStream({ |
| 395 start(c) { |
| 396 controller = c; |
| 397 } |
| 398 }); |
| 399 const reader = rs.getReader(); |
| 400 |
| 401 const promise = Promise.all([ |
| 402 reader.read().then(result => { |
| 403 assert_object_equals(result, { value: undefined, done: true }, 'read() sho
uld fulfill with close (1)'); |
| 404 }), |
| 405 reader.read().then(result => { |
| 406 assert_object_equals(result, { value: undefined, done: true }, 'read() sho
uld fulfill with close (2)'); |
| 407 }), |
| 408 reader.closed |
| 409 ]); |
| 410 |
| 411 controller.close(); |
| 412 return promise; |
| 413 |
| 414 }, 'Reading twice on a stream that gets closed'); |
| 415 |
| 416 promise_test(() => { |
| 417 |
| 418 let controller; |
| 419 const rs = new ReadableStream({ |
| 420 start(c) { |
| 421 controller = c; |
| 422 } |
| 423 }); |
| 424 |
| 425 controller.close(); |
| 426 const reader = rs.getReader(); |
| 427 |
| 428 return Promise.all([ |
| 429 reader.read().then(result => { |
| 430 assert_object_equals(result, { value: undefined, done: true }, 'read() sho
uld fulfill with close (1)'); |
| 431 }), |
| 432 reader.read().then(result => { |
| 433 assert_object_equals(result, { value: undefined, done: true }, 'read() sho
uld fulfill with close (2)'); |
| 434 }), |
| 435 reader.closed |
| 436 ]); |
| 437 |
| 438 }, 'Reading twice on a closed stream'); |
| 439 |
| 440 promise_test(t => { |
| 441 |
| 442 let controller; |
| 443 const rs = new ReadableStream({ |
| 444 start(c) { |
| 445 controller = c; |
| 446 } |
| 447 }); |
| 448 |
| 449 const myError = { name: 'mashed potatoes' }; |
| 450 controller.error(myError); |
| 451 |
| 452 const reader = rs.getReader(); |
| 453 |
| 454 return Promise.all([ |
| 455 promise_rejects(t, myError, reader.read()), |
| 456 promise_rejects(t, myError, reader.read()), |
| 457 promise_rejects(t, myError, reader.closed) |
| 458 ]); |
| 459 |
| 460 }, 'Reading twice on an errored stream'); |
| 461 |
| 462 promise_test(t => { |
| 463 |
| 464 let controller; |
| 465 const rs = new ReadableStream({ |
| 466 start(c) { |
| 467 controller = c; |
| 468 } |
| 469 }); |
| 470 |
| 471 const myError = { name: 'mashed potatoes' }; |
| 472 const reader = rs.getReader(); |
| 473 |
| 474 const promise = Promise.all([ |
| 475 promise_rejects(t, myError, reader.read()), |
| 476 promise_rejects(t, myError, reader.read()), |
| 477 promise_rejects(t, myError, reader.closed) |
| 478 ]); |
| 479 |
| 480 controller.error(myError); |
| 481 return promise; |
| 482 |
| 483 }, 'Reading twice on a stream that gets errored'); |
| 484 |
| 485 done(); |
OLD | NEW |