diff --git a/streams/readable-byte-streams/bad-buffers-and-views.any.js b/streams/readable-byte-streams/bad-buffers-and-views.any.js index eed3a5ed4f8d02..cc75dc46f9830c 100644 --- a/streams/readable-byte-streams/bad-buffers-and-views.any.js +++ b/streams/readable-byte-streams/bad-buffers-and-views.any.js @@ -203,6 +203,38 @@ async_test(t => { }, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view is zero-length on a ' + 'non-zero-length buffer (in the readable state)'); +async_test(t => { + const stream = new ReadableStream({ + pull: t.step_func_done(c => { + const view = c.byobRequest.view.subarray(1, 2); + + assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view)); + }), + type: 'bytes' + }); + const reader = stream.getReader({ mode: 'byob' }); + + reader.read(new Uint8Array([4, 5, 6])); +}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view has a different offset ' + + '(in the readable state)'); + +async_test(t => { + const stream = new ReadableStream({ + pull: t.step_func_done(c => { + c.close(); + + const view = c.byobRequest.view.subarray(1, 1); + + assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view)); + }), + type: 'bytes' + }); + const reader = stream.getReader({ mode: 'byob' }); + + reader.read(new Uint8Array([4, 5, 6])); +}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view has a different offset ' + + '(in the closed state)'); + async_test(t => { const stream = new ReadableStream({ pull: t.step_func_done(c => { diff --git a/streams/readable-byte-streams/general.any.js b/streams/readable-byte-streams/general.any.js index 23691bb56b32ef..3b88e8dd956220 100644 --- a/streams/readable-byte-streams/general.any.js +++ b/streams/readable-byte-streams/general.any.js @@ -236,15 +236,27 @@ promise_test(t => { }); }, 'ReadableStream with byte source: Test that erroring a stream does not release a BYOB reader automatically'); -test(() => { +promise_test(async t => { const stream = new ReadableStream({ type: 'bytes' }); const reader = stream.getReader(); - reader.read(); - assert_throws_js(TypeError, () => reader.releaseLock(), 'reader.releaseLock() must throw'); -}, 'ReadableStream with byte source: releaseLock() on ReadableStreamDefaultReader with pending read() must throw'); + const read = reader.read(); + reader.releaseLock(); + await promise_rejects_js(t, TypeError, read, 'pending read must reject'); +}, 'ReadableStream with byte source: releaseLock() on ReadableStreamDefaultReader must reject pending read()'); + +promise_test(async t => { + const stream = new ReadableStream({ + type: 'bytes' + }); + + const reader = stream.getReader({ mode: 'byob' }); + const read = reader.read(new Uint8Array(1)); + reader.releaseLock(); + await promise_rejects_js(t, TypeError, read, 'pending read must reject'); +}, 'ReadableStream with byte source: releaseLock() on ReadableStreamBYOBReader must reject pending read()'); promise_test(() => { let pullCount = 0; @@ -2111,7 +2123,7 @@ promise_test(() => { }); }, 'calling respond() should throw when canceled'); -promise_test(() => { +promise_test(async t => { let resolvePullCalledPromise; const pullCalledPromise = new Promise(resolve => { resolvePullCalledPromise = resolve; @@ -2127,14 +2139,13 @@ promise_test(() => { type: 'bytes' }); const reader = rs.getReader({ mode: 'byob' }); - reader.read(new Uint8Array(16)); - return pullCalledPromise.then(() => { - resolvePull(); - return delay(0).then(() => { - assert_throws_js(TypeError, () => reader.releaseLock(), 'releaseLock() should throw'); - }); - }); -}, 'pull() resolving should not make releaseLock() possible'); + const read = reader.read(new Uint8Array(16)); + await pullCalledPromise; + resolvePull(); + await delay(0); + reader.releaseLock(); + await promise_rejects_js(t, TypeError, read, 'pending read should reject'); +}, 'pull() resolving should not resolve read()'); promise_test(() => { // Tests https://github.com/whatwg/streams/issues/686 @@ -2363,7 +2374,7 @@ promise_test(async t => { assert_equals(byobRequest1.view, null, 'first byobRequest must be invalidated after enqueue()'); const result1 = await read1; - assert_equals(result1.done, false, 'first result.done'); + assert_false(result1.done, 'first result.done'); const view1 = result1.value; assert_equals(view1.byteOffset, 0, 'first result.value.byteOffset'); assert_equals(view1.byteLength, 3, 'first result.value.byteLength'); @@ -2384,7 +2395,7 @@ promise_test(async t => { assert_equals(byobRequest2.view, null, 'second byobRequest must be invalidated after respond()'); const result2 = await read2; - assert_equals(result2.done, false, 'second result.done'); + assert_false(result2.done, 'second result.done'); const view2 = result2.value; assert_equals(view2.byteOffset, 0, 'second result.value.byteOffset'); assert_equals(view2.byteLength, 3, 'second result.value.byteLength'); @@ -2393,3 +2404,475 @@ promise_test(async t => { reader2.releaseLock(); assert_equals(pullCount, 2, 'pull() must only have been invoked twice'); }, 'ReadableStream with byte source: enqueue() discards auto-allocated BYOB request'); + +promise_test(async t => { + let controller; + const rs = new ReadableStream({ + type: 'bytes', + start: t.step_func((c) => { + controller = c; + }) + }); + await flushAsyncEvents(); + + const reader1 = rs.getReader({ mode: 'byob' }); + const read1 = reader1.read(new Uint8Array([1, 2, 3])); + const byobRequest1 = controller.byobRequest; + assert_not_equals(byobRequest1, null, 'first byobRequest should exist'); + assert_typed_array_equals(byobRequest1.view, new Uint8Array([1, 2, 3]), 'first byobRequest.view'); + + // releaseLock() should reject the pending read, but *not* invalidate the BYOB request + reader1.releaseLock(); + const reader2 = rs.getReader({ mode: 'byob' }); + const read2 = reader2.read(new Uint8Array([4, 5, 6])); + assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()'); + assert_equals(controller.byobRequest, byobRequest1, 'byobRequest should be unchanged'); + assert_array_equals([...new Uint8Array(byobRequest1.view.buffer)], [1, 2, 3], 'byobRequest.view.buffer should be unchanged'); + await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()'); + + // respond() should fulfill the *second* read() request + byobRequest1.view[0] = 11; + byobRequest1.respond(1); + const byobRequest2 = controller.byobRequest; + assert_equals(byobRequest2, null, 'byobRequest should be null after respond()'); + + const result2 = await read2; + assert_false(result2.done, 'second result.done'); + assert_typed_array_equals(result2.value, new Uint8Array([11, 5, 6]).subarray(0, 1), 'second result.value'); + +}, 'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader, respond()'); + +promise_test(async t => { + let controller; + const rs = new ReadableStream({ + type: 'bytes', + start: t.step_func((c) => { + controller = c; + }) + }); + await flushAsyncEvents(); + + const reader1 = rs.getReader({ mode: 'byob' }); + const read1 = reader1.read(new Uint8Array([1, 2, 3])); + const byobRequest1 = controller.byobRequest; + assert_not_equals(byobRequest1, null, 'first byobRequest should exist'); + assert_typed_array_equals(byobRequest1.view, new Uint8Array([1, 2, 3]), 'first byobRequest.view'); + + // releaseLock() should reject the pending read, but *not* invalidate the BYOB request + reader1.releaseLock(); + const reader2 = rs.getReader({ mode: 'byob' }); + const read2 = reader2.read(new Uint16Array(1)); + assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()'); + assert_equals(controller.byobRequest, byobRequest1, 'byobRequest should be unchanged'); + assert_array_equals([...new Uint8Array(byobRequest1.view.buffer)], [1, 2, 3], 'byobRequest.view.buffer should be unchanged'); + await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()'); + + // respond(1) should partially fill the second read(), but not yet fulfill it + byobRequest1.view[0] = 0x11; + byobRequest1.respond(1); + + // second BYOB request should use remaining buffer from the second read() + const byobRequest2 = controller.byobRequest; + assert_not_equals(byobRequest2, null, 'second byobRequest should exist'); + assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11, 0]).subarray(1, 2), 'second byobRequest.view'); + + // second respond(1) should fill the read request and fulfill it + byobRequest2.view[0] = 0x22; + byobRequest2.respond(1); + const result2 = await read2; + assert_false(result2.done, 'second result.done'); + const view2 = result2.value; + assert_equals(view2.byteOffset, 0, 'second result.value.byteOffset'); + assert_equals(view2.byteLength, 2, 'second result.value.byteLength'); + const dataView2 = new DataView(view2.buffer, view2.byteOffset, view2.byteLength); + assert_equals(dataView2.getUint16(0), 0x1122, 'second result.value[0]'); + +}, 'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader with ' + + '1 element Uint16Array, respond(1)'); + +promise_test(async t => { + let controller; + const rs = new ReadableStream({ + type: 'bytes', + start: t.step_func((c) => { + controller = c; + }) + }); + await flushAsyncEvents(); + + const reader1 = rs.getReader({ mode: 'byob' }); + const read1 = reader1.read(new Uint8Array([1, 2, 3])); + const byobRequest1 = controller.byobRequest; + assert_not_equals(byobRequest1, null, 'first byobRequest should exist'); + assert_typed_array_equals(byobRequest1.view, new Uint8Array([1, 2, 3]), 'first byobRequest.view'); + + // releaseLock() should reject the pending read, but *not* invalidate the BYOB request + reader1.releaseLock(); + const reader2 = rs.getReader({ mode: 'byob' }); + const read2 = reader2.read(new Uint8Array([4, 5])); + assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()'); + assert_equals(controller.byobRequest, byobRequest1, 'byobRequest should be unchanged'); + assert_array_equals([...new Uint8Array(byobRequest1.view.buffer)], [1, 2, 3], 'byobRequest.view.buffer should be unchanged'); + await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()'); + + // respond(3) should fulfill the second read(), and put 1 remaining byte in the queue + byobRequest1.view[0] = 6; + byobRequest1.view[1] = 7; + byobRequest1.view[2] = 8; + byobRequest1.respond(3); + const byobRequest2 = controller.byobRequest; + assert_equals(byobRequest2, null, 'byobRequest should be null after respond()'); + + const result2 = await read2; + assert_false(result2.done, 'second result.done'); + assert_typed_array_equals(result2.value, new Uint8Array([6, 7]), 'second result.value'); + + // third read() should fulfill with the remaining byte + const result3 = await reader2.read(new Uint8Array([0, 0, 0])); + assert_false(result3.done, 'third result.done'); + assert_typed_array_equals(result3.value, new Uint8Array([8, 0, 0]).subarray(0, 1), 'third result.value'); + +}, 'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader with ' + + '2 element Uint8Array, respond(3)'); + +promise_test(async t => { + let controller; + const rs = new ReadableStream({ + type: 'bytes', + start: t.step_func((c) => { + controller = c; + }) + }); + await flushAsyncEvents(); + + const reader1 = rs.getReader({ mode: 'byob' }); + const read1 = reader1.read(new Uint8Array([1, 2, 3])); + const byobRequest1 = controller.byobRequest; + assert_not_equals(byobRequest1, null, 'first byobRequest should exist'); + assert_typed_array_equals(byobRequest1.view, new Uint8Array([1, 2, 3]), 'first byobRequest.view'); + + // releaseLock() should reject the pending read, but *not* invalidate the BYOB request + reader1.releaseLock(); + const reader2 = rs.getReader({ mode: 'byob' }); + const read2 = reader2.read(new Uint8Array([4, 5, 6])); + assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()'); + await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()'); + + // respondWithNewView() should fulfill the *second* read() request + byobRequest1.view[0] = 11; + byobRequest1.view[1] = 12; + byobRequest1.respondWithNewView(byobRequest1.view.subarray(0, 2)); + const byobRequest2 = controller.byobRequest; + assert_equals(byobRequest2, null, 'byobRequest should be null after respondWithNewView()'); + + const result2 = await read2; + assert_false(result2.done, 'second result.done'); + assert_typed_array_equals(result2.value, new Uint8Array([11, 12, 6]).subarray(0, 2), 'second result.value'); + +}, 'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader, respondWithNewView()'); + +promise_test(async t => { + let controller; + const rs = new ReadableStream({ + type: 'bytes', + start: t.step_func((c) => { + controller = c; + }) + }); + await flushAsyncEvents(); + + const reader1 = rs.getReader({ mode: 'byob' }); + const read1 = reader1.read(new Uint8Array([1, 2, 3])); + const byobRequest1 = controller.byobRequest; + assert_not_equals(byobRequest1, null, 'first byobRequest should exist'); + assert_typed_array_equals(byobRequest1.view, new Uint8Array([1, 2, 3]), 'first byobRequest.view'); + + // releaseLock() should reject the pending read, but *not* invalidate the BYOB request + reader1.releaseLock(); + const reader2 = rs.getReader({ mode: 'byob' }); + const read2 = reader2.read(new Uint8Array([4, 5, 6])); + assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()'); + await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()'); + + // enqueue() should fulfill the *second* read() request + controller.enqueue(new Uint8Array([11, 12])); + const byobRequest2 = controller.byobRequest; + assert_equals(byobRequest2, null, 'byobRequest should be null after enqueue()'); + + const result2 = await read2; + assert_false(result2.done, 'second result.done'); + assert_typed_array_equals(result2.value, new Uint8Array([11, 12, 6]).subarray(0, 2), 'second result.value'); + +}, 'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader, enqueue()'); + +promise_test(async t => { + let controller; + const rs = new ReadableStream({ + type: 'bytes', + start: t.step_func((c) => { + controller = c; + }) + }); + await flushAsyncEvents(); + + const reader1 = rs.getReader({ mode: 'byob' }); + const read1 = reader1.read(new Uint8Array([1, 2, 3])); + const byobRequest1 = controller.byobRequest; + assert_not_equals(byobRequest1, null, 'first byobRequest should exist'); + assert_typed_array_equals(byobRequest1.view, new Uint8Array([1, 2, 3]), 'first byobRequest.view'); + + // releaseLock() should reject the pending read, but *not* invalidate the BYOB request + reader1.releaseLock(); + const reader2 = rs.getReader({ mode: 'byob' }); + const read2 = reader2.read(new Uint8Array([4, 5, 6])); + assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()'); + await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()'); + + // close() followed by respond(0) should fulfill the second read() + controller.close(); + byobRequest1.respond(0); + const byobRequest2 = controller.byobRequest; + assert_equals(byobRequest2, null, 'byobRequest should be null after respond()'); + + const result2 = await read2; + assert_true(result2.done, 'second result.done'); + assert_typed_array_equals(result2.value, new Uint8Array([4, 5, 6]).subarray(0, 0), 'second result.value'); +}, 'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader, ' + + 'close(), respond(0)'); + +promise_test(async t => { + let controller; + const rs = new ReadableStream({ + type: 'bytes', + autoAllocateChunkSize: 4, + start: t.step_func((c) => { + controller = c; + }) + }); + await flushAsyncEvents(); + + const reader1 = rs.getReader(); + const read1 = reader1.read(); + const byobRequest1 = controller.byobRequest; + assert_not_equals(byobRequest1, null, 'first byobRequest should exist'); + assert_typed_array_equals(byobRequest1.view, new Uint8Array(4), 'first byobRequest.view'); + + // releaseLock() should reject the pending read, but *not* invalidate the BYOB request + reader1.releaseLock(); + const reader2 = rs.getReader(); + const read2 = reader2.read(); + assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()'); + await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()'); + + // respond() should fulfill the *second* read() request + byobRequest1.view[0] = 11; + byobRequest1.respond(1); + const byobRequest2 = controller.byobRequest; + assert_equals(byobRequest2, null, 'byobRequest should be null after respond()'); + + const result2 = await read2; + assert_false(result2.done, 'second result.done'); + assert_typed_array_equals(result2.value, new Uint8Array([11, 0, 0, 0]).subarray(0, 1), 'second result.value'); + +}, 'ReadableStream with byte source: autoAllocateChunkSize, releaseLock() with pending read(), read() on second reader, respond()'); + +promise_test(async t => { + let controller; + const rs = new ReadableStream({ + type: 'bytes', + autoAllocateChunkSize: 4, + start: t.step_func((c) => { + controller = c; + }) + }); + await flushAsyncEvents(); + + const reader1 = rs.getReader(); + const read1 = reader1.read(); + const byobRequest1 = controller.byobRequest; + assert_not_equals(byobRequest1, null, 'first byobRequest should exist'); + assert_typed_array_equals(byobRequest1.view, new Uint8Array(4), 'first byobRequest.view'); + + // releaseLock() should reject the pending read, but *not* invalidate the BYOB request + reader1.releaseLock(); + const reader2 = rs.getReader(); + const read2 = reader2.read(); + assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()'); + await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()'); + + // enqueue() should fulfill the *second* read() request + controller.enqueue(new Uint8Array([11])); + const byobRequest2 = controller.byobRequest; + assert_equals(byobRequest2, null, 'byobRequest should be null after enqueue()'); + + const result2 = await read2; + assert_false(result2.done, 'second result.done'); + assert_typed_array_equals(result2.value, new Uint8Array([11]), 'second result.value'); + +}, 'ReadableStream with byte source: autoAllocateChunkSize, releaseLock() with pending read(), read() on second reader, enqueue()'); + +promise_test(async t => { + let controller; + const rs = new ReadableStream({ + type: 'bytes', + autoAllocateChunkSize: 4, + start: t.step_func((c) => { + controller = c; + }) + }); + await flushAsyncEvents(); + + const reader1 = rs.getReader(); + const read1 = reader1.read(); + const byobRequest1 = controller.byobRequest; + assert_not_equals(byobRequest1, null, 'first byobRequest should exist'); + assert_typed_array_equals(byobRequest1.view, new Uint8Array(4), 'first byobRequest.view'); + + // releaseLock() should reject the pending read, but *not* invalidate the BYOB request + reader1.releaseLock(); + const reader2 = rs.getReader({ mode: 'byob' }); + const read2 = reader2.read(new Uint8Array([4, 5, 6])); + assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()'); + await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()'); + + // respond() should fulfill the *second* read() request + byobRequest1.view[0] = 11; + byobRequest1.respond(1); + const byobRequest2 = controller.byobRequest; + assert_equals(byobRequest2, null, 'byobRequest should be null after respond()'); + + const result2 = await read2; + assert_false(result2.done, 'second result.done'); + assert_typed_array_equals(result2.value, new Uint8Array([11, 5, 6]).subarray(0, 1), 'second result.value'); + +}, 'ReadableStream with byte source: autoAllocateChunkSize, releaseLock() with pending read(), read(view) on second reader, respond()'); + +promise_test(async t => { + let controller; + const rs = new ReadableStream({ + type: 'bytes', + autoAllocateChunkSize: 4, + start: t.step_func((c) => { + controller = c; + }) + }); + await flushAsyncEvents(); + + const reader1 = rs.getReader(); + const read1 = reader1.read(); + const byobRequest1 = controller.byobRequest; + assert_not_equals(byobRequest1, null, 'first byobRequest should exist'); + assert_typed_array_equals(byobRequest1.view, new Uint8Array(4), 'first byobRequest.view'); + + // releaseLock() should reject the pending read, but *not* invalidate the BYOB request + reader1.releaseLock(); + const reader2 = rs.getReader({ mode: 'byob' }); + const read2 = reader2.read(new Uint8Array([4, 5, 6])); + assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()'); + await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()'); + + // enqueue() should fulfill the *second* read() request + controller.enqueue(new Uint8Array([11])); + const byobRequest2 = controller.byobRequest; + assert_equals(byobRequest2, null, 'byobRequest should be null after enqueue()'); + + const result2 = await read2; + assert_false(result2.done, 'second result.done'); + assert_typed_array_equals(result2.value, new Uint8Array([11, 5, 6]).subarray(0, 1), 'second result.value'); + +}, 'ReadableStream with byte source: autoAllocateChunkSize, releaseLock() with pending read(), read(view) on second reader, enqueue()'); + +promise_test(async t => { + let controller; + const rs = new ReadableStream({ + type: 'bytes', + start: t.step_func((c) => { + controller = c; + }) + }); + await flushAsyncEvents(); + + const reader1 = rs.getReader({ mode: 'byob' }); + const read1 = reader1.read(new Uint16Array(1)); + const byobRequest1 = controller.byobRequest; + assert_not_equals(byobRequest1, null, 'first byobRequest should exist'); + assert_typed_array_equals(byobRequest1.view, new Uint8Array([0, 0]), 'first byobRequest.view'); + + // respond(1) should partially fill the first read(), but not yet fulfill it + byobRequest1.view[0] = 0x11; + byobRequest1.respond(1); + const byobRequest2 = controller.byobRequest; + assert_not_equals(byobRequest2, null, 'second byobRequest should exist'); + assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11, 0]).subarray(1, 2), 'second byobRequest.view'); + + // releaseLock() should reject the pending read, but *not* invalidate the BYOB request + reader1.releaseLock(); + const reader2 = rs.getReader({ mode: 'byob' }); + const read2 = reader2.read(new Uint16Array(1)); + assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()'); + assert_equals(controller.byobRequest, byobRequest2, 'byobRequest should be unchanged'); + assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11, 0]).subarray(1, 2), 'byobRequest.view should be unchanged'); + await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()'); + + // second respond(1) should fill the read request and fulfill it + byobRequest2.view[0] = 0x22; + byobRequest2.respond(1); + assert_equals(controller.byobRequest, null, 'byobRequest should be invalidated after second respond()'); + + const result2 = await read2; + assert_false(result2.done, 'second result.done'); + const view2 = result2.value; + assert_equals(view2.byteOffset, 0, 'second result.value.byteOffset'); + assert_equals(view2.byteLength, 2, 'second result.value.byteLength'); + const dataView2 = new DataView(view2.buffer, view2.byteOffset, view2.byteLength); + assert_equals(dataView2.getUint16(0), 0x1122, 'second result.value[0]'); + +}, 'ReadableStream with byte source: read(view) with 1 element Uint16Array, respond(1), releaseLock(), read(view) on ' + + 'second reader with 1 element Uint16Array, respond(1)'); + +promise_test(async t => { + let controller; + const rs = new ReadableStream({ + type: 'bytes', + start: t.step_func((c) => { + controller = c; + }) + }); + await flushAsyncEvents(); + + const reader1 = rs.getReader({ mode: 'byob' }); + const read1 = reader1.read(new Uint16Array(1)); + const byobRequest1 = controller.byobRequest; + assert_not_equals(byobRequest1, null, 'first byobRequest should exist'); + assert_typed_array_equals(byobRequest1.view, new Uint8Array([0, 0]), 'first byobRequest.view'); + + // respond(1) should partially fill the first read(), but not yet fulfill it + byobRequest1.view[0] = 0x11; + byobRequest1.respond(1); + const byobRequest2 = controller.byobRequest; + assert_not_equals(byobRequest2, null, 'second byobRequest should exist'); + assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11, 0]).subarray(1, 2), 'second byobRequest.view'); + + // releaseLock() should reject the pending read, but *not* invalidate the BYOB request + reader1.releaseLock(); + const reader2 = rs.getReader(); + const read2 = reader2.read(); + assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()'); + assert_equals(controller.byobRequest, byobRequest2, 'byobRequest should be unchanged'); + assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11, 0]).subarray(1, 2), 'byobRequest.view should be unchanged'); + await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()'); + + // enqueue() should fulfill the read request and put remaining byte in the queue + controller.enqueue(new Uint8Array([0x22])); + assert_equals(controller.byobRequest, null, 'byobRequest should be invalidated after second respond()'); + + const result2 = await read2; + assert_false(result2.done, 'second result.done'); + assert_typed_array_equals(result2.value, new Uint8Array([0x11]), 'second result.value'); + + const result3 = await reader2.read(); + assert_false(result3.done, 'third result.done'); + assert_typed_array_equals(result3.value, new Uint8Array([0x22]), 'third result.value'); + +}, 'ReadableStream with byte source: read(view) with 1 element Uint16Array, respond(1), releaseLock(), read() on ' + + 'second reader, enqueue()'); diff --git a/streams/readable-byte-streams/tee.any.js b/streams/readable-byte-streams/tee.any.js index be1da13861ef71..13f03f06bc44f1 100644 --- a/streams/readable-byte-streams/tee.any.js +++ b/streams/readable-byte-streams/tee.any.js @@ -5,17 +5,6 @@ // META: script=../resources/rs-test-templates.js 'use strict'; -function assert_typed_array_equals(actual, expected, message) { - const prefix = message === undefined ? '' : `${message} `; - assert_equals(typeof actual, 'object', `${prefix}type is object`); - assert_equals(actual.constructor, expected.constructor, `${prefix}constructor`); - assert_equals(actual.byteOffset, expected.byteOffset, `${prefix}byteOffset`); - assert_equals(actual.byteLength, expected.byteLength, `${prefix}byteLength`); - assert_equals(actual.buffer.byteLength, expected.buffer.byteLength, `${prefix}buffer.byteLength`); - assert_array_equals([...actual], [...expected], `${prefix}contents`); - assert_array_equals([...new Uint8Array(actual.buffer)], [...new Uint8Array(expected.buffer)], `${prefix}buffer contents`); -} - test(() => { const rs = new ReadableStream({ type: 'bytes' }); diff --git a/streams/readable-streams/default-reader.any.js b/streams/readable-streams/default-reader.any.js index 60c740a8288631..664853e28cfb9f 100644 --- a/streams/readable-streams/default-reader.any.js +++ b/streams/readable-streams/default-reader.any.js @@ -512,3 +512,28 @@ promise_test(() => { reader.releaseLock(); }); }, 'controller.close() should clear the list of pending read requests'); + +promise_test(t => { + + let controller; + const rs = new ReadableStream({ + start(c) { + controller = c; + } + }); + + const reader1 = rs.getReader(); + const promise1 = promise_rejects_js(t, TypeError, reader1.read(), 'read() from reader1 should reject when reader1 is released'); + reader1.releaseLock(); + + controller.enqueue('a'); + + const reader2 = rs.getReader(); + const promise2 = reader2.read().then(r => { + assert_object_equals(r, { value: 'a', done: false }, 'read() from reader2 should resolve with enqueued chunk'); + }) + reader2.releaseLock(); + + return Promise.all([promise1, promise2]); + +}, 'Second reader can read chunks after first reader was released with pending read requests'); diff --git a/streams/resources/rs-test-templates.js b/streams/resources/rs-test-templates.js index 400907a9db2908..25751c477f5dc8 100644 --- a/streams/resources/rs-test-templates.js +++ b/streams/resources/rs-test-templates.js @@ -251,34 +251,27 @@ self.templatedRSEmptyReader = (label, factory) => { }, label + ': getReader() again on the stream should fail'); - promise_test(t => { + promise_test(async t => { const streamAndReader = factory(); const stream = streamAndReader.stream; const reader = streamAndReader.reader; - reader.read().then( - t.unreached_func('first read() should not fulfill'), - t.unreached_func('first read() should not reject') - ); - - reader.read().then( - t.unreached_func('second read() should not fulfill'), - t.unreached_func('second read() should not reject') - ); + const read1 = reader.read(); + const read2 = reader.read(); + const closed = reader.closed; - reader.closed.then( - t.unreached_func('closed should not fulfill'), - t.unreached_func('closed should not reject') - ); - - assert_throws_js(TypeError, () => reader.releaseLock(), 'releaseLock should throw a TypeError'); + reader.releaseLock(); - assert_true(stream.locked, 'the stream should still be locked'); + assert_false(stream.locked, 'the stream should be unlocked'); - return delay(500); + await Promise.all([ + promise_rejects_js(t, TypeError, read1, 'first read should reject'), + promise_rejects_js(t, TypeError, read2, 'second read should reject'), + promise_rejects_js(t, TypeError, closed, 'closed should reject') + ]); - }, label + ': releasing the lock with pending read requests should throw but the read requests should stay pending'); + }, label + ': releasing the lock should reject all pending read requests'); promise_test(t => { diff --git a/streams/resources/test-utils.js b/streams/resources/test-utils.js index 0593980e1055b5..0b112a377bd1a0 100644 --- a/streams/resources/test-utils.js +++ b/streams/resources/test-utils.js @@ -72,3 +72,14 @@ self.delay = ms => new Promise(resolve => step_timeout(resolve, ms)); // Some tests include promise resolutions which may mean the test code takes a couple of event loop visits itself. So go // around an extra 2 times to avoid complicating those tests. self.flushAsyncEvents = () => delay(0).then(() => delay(0)).then(() => delay(0)).then(() => delay(0)); + +self.assert_typed_array_equals = (actual, expected, message) => { + const prefix = message === undefined ? '' : `${message} `; + assert_equals(typeof actual, 'object', `${prefix}type is object`); + assert_equals(actual.constructor, expected.constructor, `${prefix}constructor`); + assert_equals(actual.byteOffset, expected.byteOffset, `${prefix}byteOffset`); + assert_equals(actual.byteLength, expected.byteLength, `${prefix}byteLength`); + assert_equals(actual.buffer.byteLength, expected.buffer.byteLength, `${prefix}buffer.byteLength`); + assert_array_equals([...actual], [...expected], `${prefix}contents`); + assert_array_equals([...new Uint8Array(actual.buffer)], [...new Uint8Array(expected.buffer)], `${prefix}buffer contents`); +};