mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
test: update WPT for streams to f8f26a372f
PR-URL: https://github.com/nodejs/node/pull/62864 Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
This commit is contained in:
1 parent
d44a71a251
commit
117892603c
28 files changed
+820
-45
No files matched your search
Vendored
+1
-1
@@ -27,7 +27,7 @@ Last update:
|
||||
- performance-timeline: https://github.com/web-platform-tests/wpt/tree/94caab7038/performance-timeline
|
||||
- resource-timing: https://github.com/web-platform-tests/wpt/tree/22d38586d0/resource-timing
|
||||
- resources: https://github.com/web-platform-tests/wpt/tree/6a2f322376/resources
|
||||
- streams: https://github.com/web-platform-tests/wpt/tree/bc9dcbbf1a/streams
|
||||
- streams: https://github.com/web-platform-tests/wpt/tree/f8f26a372f/streams
|
||||
- url: https://github.com/web-platform-tests/wpt/tree/7a3645b79a/url
|
||||
- urlpattern: https://github.com/web-platform-tests/wpt/tree/f07c03cbed/urlpattern
|
||||
- user-timing: https://github.com/web-platform-tests/wpt/tree/5ae85bf826/user-timing
|
||||
|
||||
Vendored
+1
-2
@@ -1,7 +1,6 @@
|
||||
spec: https://streams.spec.whatwg.org/
|
||||
suggested_reviewers:
|
||||
- domenic
|
||||
- yutakahirano
|
||||
- youennf
|
||||
- wanderview
|
||||
- ricea
|
||||
- MattiasBuelens
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
features:
|
||||
- name: streams
|
||||
files:
|
||||
# Top-level only. Subdirectories have their own mapping.
|
||||
- "*"
|
||||
@@ -0,0 +1,3 @@
|
||||
features:
|
||||
- name: streams
|
||||
files: "**"
|
||||
@@ -0,0 +1,3 @@
|
||||
features:
|
||||
- name: readable-byte-streams
|
||||
files: "**"
|
||||
+5
-12
@@ -123,8 +123,7 @@ promise_test(t => {
|
||||
async_test(t => {
|
||||
const stream = new ReadableStream({
|
||||
pull: t.step_func_done(c => {
|
||||
// Detach it by reading into it
|
||||
reader.read(c.byobRequest.view);
|
||||
c.byobRequest.view.buffer.transfer();
|
||||
|
||||
assert_throws_js(TypeError, () => c.byobRequest.respond(1),
|
||||
'respond() must throw if the corresponding view has become detached');
|
||||
@@ -141,9 +140,7 @@ async_test(t => {
|
||||
const stream = new ReadableStream({
|
||||
pull: t.step_func_done(c => {
|
||||
c.close();
|
||||
|
||||
// Detach it by reading into it
|
||||
reader.read(c.byobRequest.view);
|
||||
c.byobRequest.view.buffer.transfer();
|
||||
|
||||
assert_throws_js(TypeError, () => c.byobRequest.respond(0),
|
||||
'respond() must throw if the corresponding view has become detached');
|
||||
@@ -159,9 +156,8 @@ async_test(t => {
|
||||
async_test(t => {
|
||||
const stream = new ReadableStream({
|
||||
pull: t.step_func_done(c => {
|
||||
// Detach it by reading into it
|
||||
const view = new Uint8Array([1, 2, 3]);
|
||||
reader.read(view);
|
||||
view.buffer.transfer();
|
||||
|
||||
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
|
||||
}),
|
||||
@@ -364,8 +360,7 @@ async_test(t => {
|
||||
async_test(t => {
|
||||
const stream = new ReadableStream({
|
||||
pull: t.step_func_done(c => {
|
||||
// Detach it by reading into it
|
||||
reader.read(c.byobRequest.view);
|
||||
c.byobRequest.view.buffer.transfer();
|
||||
|
||||
assert_throws_js(TypeError, () => c.enqueue(new Uint8Array([1])),
|
||||
'enqueue() must throw if the BYOB request\'s buffer has become detached');
|
||||
@@ -382,9 +377,7 @@ async_test(t => {
|
||||
const stream = new ReadableStream({
|
||||
pull: t.step_func_done(c => {
|
||||
c.close();
|
||||
|
||||
// Detach it by reading into it
|
||||
reader.read(c.byobRequest.view);
|
||||
c.byobRequest.view.buffer.transfer();
|
||||
|
||||
assert_throws_js(TypeError, () => c.enqueue(new Uint8Array([1])),
|
||||
'enqueue() must throw if the BYOB request\'s buffer has become detached');
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// META: global=window,worker
|
||||
'use strict';
|
||||
|
||||
test(() => {
|
||||
const byteReadable = new ReadableStream({type: 'bytes'});
|
||||
byteReadable.getReader();
|
||||
assert_throws_js(TypeError, () => byteReadable.tee(), 'byteReadable.tee() must throw');
|
||||
}, 'tee() on a locked byte stream does not crash');
|
||||
|
||||
@@ -236,6 +236,29 @@ promise_test(t => {
|
||||
});
|
||||
}, 'ReadableStream with byte source: Test that erroring a stream does not release a BYOB reader automatically');
|
||||
|
||||
promise_test(async t => {
|
||||
const rs = new ReadableStream({
|
||||
type: 'bytes',
|
||||
start(c) {
|
||||
c.enqueue(new Uint8Array([1, 2, 3]));
|
||||
}
|
||||
});
|
||||
|
||||
const reader1 = rs.getReader({mode: 'byob'});
|
||||
reader1.releaseLock();
|
||||
|
||||
const reader2 = rs.getReader({mode: 'byob'});
|
||||
|
||||
// Should be a no-op
|
||||
reader1.releaseLock();
|
||||
|
||||
const result = await reader2.read(new Uint8Array([0, 0, 0]));
|
||||
assert_typed_array_equals(result.value, new Uint8Array([1, 2, 3]),
|
||||
'read() should still work on reader2 even after reader1 is released');
|
||||
assert_false(result.done, 'done');
|
||||
|
||||
}, 'ReadableStream with byte source: cannot use an already-released BYOB reader to unlock a stream again');
|
||||
|
||||
promise_test(async t => {
|
||||
const stream = new ReadableStream({
|
||||
type: 'bytes'
|
||||
@@ -992,6 +1015,69 @@ promise_test(() => {
|
||||
}, 'ReadableStream with byte source: respond(3) to read(view) with 2 element Uint16Array enqueues the 1 byte ' +
|
||||
'remainder');
|
||||
|
||||
promise_test(() => {
|
||||
let pullCount = 0;
|
||||
|
||||
let controller;
|
||||
let byobRequest;
|
||||
let viewInfo;
|
||||
|
||||
const stream = new ReadableStream({
|
||||
start(c) {
|
||||
controller = c;
|
||||
},
|
||||
pull() {
|
||||
++pullCount;
|
||||
|
||||
byobRequest = controller.byobRequest;
|
||||
const view = byobRequest.view;
|
||||
viewInfo = extractViewInfo(view);
|
||||
|
||||
view[0] = 0x01;
|
||||
view[1] = 0x02;
|
||||
view[2] = 0x03;
|
||||
|
||||
controller.byobRequest.respond(3);
|
||||
},
|
||||
type: 'bytes'
|
||||
});
|
||||
|
||||
const reader = stream.getReader({ mode: 'byob' });
|
||||
const read1 = reader.read(new Uint16Array(2));
|
||||
const read2 = reader.read(new Uint8Array(1));
|
||||
|
||||
return read1.then(result => {
|
||||
assert_equals(pullCount, 1);
|
||||
|
||||
assert_false(result.done, 'done');
|
||||
|
||||
const view = result.value;
|
||||
assert_equals(view.byteOffset, 0, 'byteOffset');
|
||||
assert_equals(view.byteLength, 2, 'byteLength');
|
||||
|
||||
const dataView = new DataView(view.buffer, view.byteOffset, view.byteLength);
|
||||
assert_equals(dataView.getUint16(0), 0x0102);
|
||||
|
||||
return read2;
|
||||
}).then(result => {
|
||||
assert_equals(pullCount, 1);
|
||||
assert_not_equals(byobRequest, null, 'byobRequest must not be null');
|
||||
assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
|
||||
assert_equals(viewInfo.bufferByteLength, 4, 'view.buffer.byteLength should be 4');
|
||||
assert_equals(viewInfo.byteOffset, 0, 'view.byteOffset should be 0');
|
||||
assert_equals(viewInfo.byteLength, 4, 'view.byteLength should be 4');
|
||||
|
||||
assert_false(result.done, 'done');
|
||||
|
||||
const view = result.value;
|
||||
assert_equals(view.byteOffset, 0, 'byteOffset');
|
||||
assert_equals(view.byteLength, 1, 'byteLength');
|
||||
|
||||
assert_equals(view[0], 0x03);
|
||||
});
|
||||
}, 'ReadableStream with byte source: respond(3) to read(view) with 2 element Uint16Array fulfills second read(view) ' +
|
||||
'with the 1 byte remainder');
|
||||
|
||||
promise_test(t => {
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// META: global=window,worker,shadowrealm
|
||||
// META: script=../resources/test-utils.js
|
||||
// META: script=../resources/rs-test-templates.js
|
||||
'use strict';
|
||||
|
||||
templatedRSEmpty('ReadableStream with byte source (empty)', () => {
|
||||
return new ReadableStream({ type: 'bytes' });
|
||||
});
|
||||
|
||||
templatedRSEmptyReader('ReadableStream with byte source (empty) default reader', () => {
|
||||
const stream = new ReadableStream({ type: 'bytes' });
|
||||
const reader = stream.getReader();
|
||||
return { stream, reader, read: () => reader.read() };
|
||||
});
|
||||
|
||||
templatedRSEmptyReader('ReadableStream with byte source (empty) BYOB reader', () => {
|
||||
const stream = new ReadableStream({ type: 'bytes' });
|
||||
const reader = stream.getReader({ mode: 'byob' });
|
||||
return { stream, reader, read: () => reader.read(new Uint8Array([0])) };
|
||||
});
|
||||
|
||||
templatedRSThrowAfterCloseOrError('ReadableStream with byte source', (extras) => {
|
||||
return new ReadableStream({ type: 'bytes', ...extras });
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
features:
|
||||
- name: streams
|
||||
files:
|
||||
- "*"
|
||||
- "!async-iterator.any.js"
|
||||
- "!from.any.js"
|
||||
# 'owning' type is not yet standardized
|
||||
- "!owning-type*"
|
||||
- name: async-iterable-streams
|
||||
files:
|
||||
- async-iterator.any.js
|
||||
- name: readablestream-from
|
||||
files:
|
||||
- from.any.js
|
||||
@@ -0,0 +1,7 @@
|
||||
features:
|
||||
- name: streams
|
||||
files:
|
||||
- strategy-worker-terminate.html
|
||||
- name: readablestream-from
|
||||
files:
|
||||
- from-cross-realm.https.html
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="test-wait">
|
||||
<meta charset="utf-8">
|
||||
<title>ReadableStream size algorithm throws during enqueue</title>
|
||||
<script>
|
||||
let throwOnSize = false;
|
||||
let pullCount = 0;
|
||||
const rs = new ReadableStream(
|
||||
{
|
||||
pull(controller) {
|
||||
pullCount++;
|
||||
if (pullCount <= 2) {
|
||||
controller.enqueue({});
|
||||
} else {
|
||||
throwOnSize = true;
|
||||
try {
|
||||
controller.enqueue({});
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
size() {
|
||||
if (throwOnSize) {
|
||||
throw new Error("size algorithm threw");
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
highWaterMark: 10,
|
||||
}
|
||||
);
|
||||
new Response(rs).text().catch(() => {
|
||||
document.documentElement.classList.remove("test-wait");
|
||||
});
|
||||
</script>
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// META: global=window,worker
|
||||
// META: script=/common/gc.js
|
||||
'use strict';
|
||||
|
||||
// See https://crbug.com/335506658 for details.
|
||||
promise_test(async () => {
|
||||
const closed = new ReadableStream({
|
||||
pull(controller) {
|
||||
controller.enqueue('is there anybody in there?');
|
||||
}
|
||||
}).getReader().closed;
|
||||
// 3 GCs are actually required to trigger the bug at time of writing.
|
||||
for (let i = 0; i < 5; ++i)
|
||||
await garbageCollect();
|
||||
}, 'Garbage-collecting a stream along with its reader should not crash');
|
||||
|
||||
promise_test(async () => {
|
||||
let reader = new ReadableStream({
|
||||
pull() { }
|
||||
}).getReader();
|
||||
const promise = reader.read();
|
||||
reader = null;
|
||||
for (let i = 0; i < 5; ++i)
|
||||
await garbageCollect();
|
||||
}, 'Garbage-collecting a stream with a pending read should not crash');
|
||||
|
||||
promise_test(async () => {
|
||||
let reader = new ReadableStream({
|
||||
type: "bytes",
|
||||
pull() { return new Promise(resolve => {}); }
|
||||
}).getReader({mode: "byob"});
|
||||
const promise = reader.read(new Uint8Array(42));
|
||||
reader = null;
|
||||
for (let i = 0; i < 5; ++i)
|
||||
await garbageCollect();
|
||||
}, 'Garbage-collecting a stream with a pending BYOB read should not crash');
|
||||
|
||||
|
||||
@@ -150,6 +150,12 @@ const badIterables = [
|
||||
['an object with a non-callable @@asyncIterator method', {
|
||||
[Symbol.asyncIterator]: 42
|
||||
}],
|
||||
['an object with an @@iterator method returning a non-object', {
|
||||
[Symbol.iterator]: () => 42
|
||||
}],
|
||||
['an object with an @@asyncIterator method returning a non-object', {
|
||||
[Symbol.asyncIterator]: () => 42
|
||||
}],
|
||||
];
|
||||
|
||||
for (const [label, iterable] of badIterables) {
|
||||
@@ -244,6 +250,64 @@ promise_test(async t => {
|
||||
|
||||
}, `ReadableStream.from: stream errors when next() rejects`);
|
||||
|
||||
promise_test(async t => {
|
||||
const theError = new Error('a unique string');
|
||||
|
||||
const iterable = {
|
||||
next() {
|
||||
throw theError;
|
||||
},
|
||||
[Symbol.asyncIterator]: () => iterable
|
||||
};
|
||||
|
||||
const rs = ReadableStream.from(iterable);
|
||||
const reader = rs.getReader();
|
||||
|
||||
await Promise.all([
|
||||
promise_rejects_exactly(t, theError, reader.read()),
|
||||
promise_rejects_exactly(t, theError, reader.closed)
|
||||
]);
|
||||
|
||||
}, 'ReadableStream.from: stream errors when next() throws synchronously');
|
||||
|
||||
promise_test(async t => {
|
||||
|
||||
const iterable = {
|
||||
next() {
|
||||
return 42; // not a promise or an iterator result
|
||||
},
|
||||
[Symbol.asyncIterator]: () => iterable
|
||||
};
|
||||
|
||||
const rs = ReadableStream.from(iterable);
|
||||
const reader = rs.getReader();
|
||||
|
||||
await Promise.all([
|
||||
promise_rejects_js(t, TypeError, reader.read()),
|
||||
promise_rejects_js(t, TypeError, reader.closed)
|
||||
]);
|
||||
|
||||
}, 'ReadableStream.from: stream errors when next() returns a non-object');
|
||||
|
||||
promise_test(async t => {
|
||||
|
||||
const iterable = {
|
||||
next() {
|
||||
return Promise.resolve(42); // not an iterator result
|
||||
},
|
||||
[Symbol.asyncIterator]: () => iterable
|
||||
};
|
||||
|
||||
const rs = ReadableStream.from(iterable);
|
||||
const reader = rs.getReader();
|
||||
|
||||
await Promise.all([
|
||||
promise_rejects_js(t, TypeError, reader.read()),
|
||||
promise_rejects_js(t, TypeError, reader.closed)
|
||||
]);
|
||||
|
||||
}, 'ReadableStream.from: stream errors when next() fulfills with a non-object');
|
||||
|
||||
promise_test(async t => {
|
||||
|
||||
const iterable = {
|
||||
@@ -360,6 +424,93 @@ promise_test(async t => {
|
||||
|
||||
}, `ReadableStream.from: return() is not called when iterator completes normally`);
|
||||
|
||||
promise_test(async t => {
|
||||
|
||||
const theError = new Error('a unique string');
|
||||
|
||||
const iterable = {
|
||||
next: t.unreached_func('next() should not be called'),
|
||||
throw: t.unreached_func('throw() should not be called'),
|
||||
// no return method
|
||||
[Symbol.asyncIterator]: () => iterable
|
||||
};
|
||||
|
||||
const rs = ReadableStream.from(iterable);
|
||||
const reader = rs.getReader();
|
||||
|
||||
await Promise.all([
|
||||
reader.cancel(theError),
|
||||
reader.closed
|
||||
]);
|
||||
|
||||
}, `ReadableStream.from: cancel() resolves when return() method is missing`);
|
||||
|
||||
promise_test(async t => {
|
||||
|
||||
const theError = new Error('a unique string');
|
||||
|
||||
const iterable = {
|
||||
next: t.unreached_func('next() should not be called'),
|
||||
throw: t.unreached_func('throw() should not be called'),
|
||||
return: 42,
|
||||
[Symbol.asyncIterator]: () => iterable
|
||||
};
|
||||
|
||||
const rs = ReadableStream.from(iterable);
|
||||
const reader = rs.getReader();
|
||||
|
||||
await promise_rejects_js(t, TypeError, reader.cancel(theError), 'cancel() should reject with a TypeError');
|
||||
|
||||
await reader.closed;
|
||||
|
||||
}, `ReadableStream.from: cancel() rejects when return() is not a method`);
|
||||
|
||||
promise_test(async t => {
|
||||
|
||||
const cancelReason = new Error('cancel reason');
|
||||
const rejectError = new Error('reject error');
|
||||
|
||||
const iterable = {
|
||||
next: t.unreached_func('next() should not be called'),
|
||||
throw: t.unreached_func('throw() should not be called'),
|
||||
async return() {
|
||||
throw rejectError;
|
||||
},
|
||||
[Symbol.asyncIterator]: () => iterable
|
||||
};
|
||||
|
||||
const rs = ReadableStream.from(iterable);
|
||||
const reader = rs.getReader();
|
||||
|
||||
await promise_rejects_exactly(t, rejectError, reader.cancel(cancelReason), 'cancel() should reject with error from return()');
|
||||
|
||||
await reader.closed;
|
||||
|
||||
}, `ReadableStream.from: cancel() rejects when return() rejects`);
|
||||
|
||||
promise_test(async t => {
|
||||
|
||||
const cancelReason = new Error('cancel reason');
|
||||
const rejectError = new Error('reject error');
|
||||
|
||||
const iterable = {
|
||||
next: t.unreached_func('next() should not be called'),
|
||||
throw: t.unreached_func('throw() should not be called'),
|
||||
return() {
|
||||
throw rejectError;
|
||||
},
|
||||
[Symbol.asyncIterator]: () => iterable
|
||||
};
|
||||
|
||||
const rs = ReadableStream.from(iterable);
|
||||
const reader = rs.getReader();
|
||||
|
||||
await promise_rejects_exactly(t, rejectError, reader.cancel(cancelReason), 'cancel() should reject with error from return()');
|
||||
|
||||
await reader.closed;
|
||||
|
||||
}, `ReadableStream.from: cancel() rejects when return() throws synchronously`);
|
||||
|
||||
promise_test(async t => {
|
||||
|
||||
const theError = new Error('a unique string');
|
||||
|
||||
@@ -69,3 +69,22 @@ promise_test(async () => {
|
||||
'old reader should still be locking the stream even after garbage collection'));
|
||||
|
||||
}, 'Garbage-collecting a ReadableStreamDefaultReader should not unlock its stream');
|
||||
|
||||
promise_test(async () => {
|
||||
|
||||
const promise = (() => {
|
||||
const rs = new ReadableStream({
|
||||
pull(controller) {
|
||||
controller.enqueue('words');
|
||||
}
|
||||
});
|
||||
const reader = rs.getReader();
|
||||
return reader.read();
|
||||
})();
|
||||
await garbageCollect();
|
||||
const {value, done} = await promise;
|
||||
// If we get here, the test passed.
|
||||
assert_equals(value, 'words', 'value should be words');
|
||||
assert_false(done, 'we should not be done');
|
||||
|
||||
}, 'A ReadableStream and its reader should not be garbage collected while there is a read promise pending');
|
||||
@@ -13,7 +13,9 @@ templatedRSEmpty('ReadableStream (empty)', () => {
|
||||
});
|
||||
|
||||
templatedRSEmptyReader('ReadableStream (empty) reader', () => {
|
||||
return streamAndDefaultReader(new ReadableStream());
|
||||
const stream = new ReadableStream();
|
||||
const reader = stream.getReader();
|
||||
return { stream, reader, read: () => reader.read() };
|
||||
});
|
||||
|
||||
templatedRSClosed('ReadableStream (closed via call in start)', () => {
|
||||
@@ -138,6 +140,10 @@ templatedRSTwoChunksClosedReader('ReadableStream (two chunks enqueued, then clos
|
||||
return result;
|
||||
}, chunks);
|
||||
|
||||
templatedRSThrowAfterCloseOrError('ReadableStream', (extras) => {
|
||||
return new ReadableStream({ ...extras });
|
||||
});
|
||||
|
||||
function streamAndDefaultReader(stream) {
|
||||
return { stream, reader: stream.getReader() };
|
||||
}
|
||||
+79
-24
@@ -200,7 +200,7 @@ self.templatedRSEmptyReader = (label, factory) => {
|
||||
|
||||
test(() => {
|
||||
|
||||
const stream = factory().stream;
|
||||
const { stream } = factory();
|
||||
|
||||
assert_true(stream.locked, 'locked getter should return true');
|
||||
|
||||
@@ -208,9 +208,9 @@ self.templatedRSEmptyReader = (label, factory) => {
|
||||
|
||||
promise_test(t => {
|
||||
|
||||
const reader = factory().reader;
|
||||
const { read } = factory();
|
||||
|
||||
reader.read().then(
|
||||
read().then(
|
||||
t.unreached_func('read() should not fulfill'),
|
||||
t.unreached_func('read() should not reject')
|
||||
);
|
||||
@@ -221,14 +221,14 @@ self.templatedRSEmptyReader = (label, factory) => {
|
||||
|
||||
promise_test(t => {
|
||||
|
||||
const reader = factory().reader;
|
||||
const { read } = factory();
|
||||
|
||||
reader.read().then(
|
||||
read().then(
|
||||
t.unreached_func('read() should not fulfill'),
|
||||
t.unreached_func('read() should not reject')
|
||||
);
|
||||
|
||||
reader.read().then(
|
||||
read().then(
|
||||
t.unreached_func('read() should not fulfill'),
|
||||
t.unreached_func('read() should not reject')
|
||||
);
|
||||
@@ -239,26 +239,24 @@ self.templatedRSEmptyReader = (label, factory) => {
|
||||
|
||||
test(() => {
|
||||
|
||||
const reader = factory().reader;
|
||||
assert_not_equals(reader.read(), reader.read(), 'the promises returned should be distinct');
|
||||
const { read } = factory();
|
||||
assert_not_equals(read(), read(), 'the promises returned should be distinct');
|
||||
|
||||
}, label + ': read() should return distinct promises each time');
|
||||
|
||||
test(() => {
|
||||
|
||||
const stream = factory().stream;
|
||||
const { stream } = factory();
|
||||
assert_throws_js(TypeError, () => stream.getReader(), 'stream.getReader() should throw a TypeError');
|
||||
|
||||
}, label + ': getReader() again on the stream should fail');
|
||||
|
||||
promise_test(async t => {
|
||||
|
||||
const streamAndReader = factory();
|
||||
const stream = streamAndReader.stream;
|
||||
const reader = streamAndReader.reader;
|
||||
const { stream, reader, read } = factory();
|
||||
|
||||
const read1 = reader.read();
|
||||
const read2 = reader.read();
|
||||
const read1 = read();
|
||||
const read2 = read();
|
||||
const closed = reader.closed;
|
||||
|
||||
reader.releaseLock();
|
||||
@@ -275,19 +273,19 @@ self.templatedRSEmptyReader = (label, factory) => {
|
||||
|
||||
promise_test(t => {
|
||||
|
||||
const reader = factory().reader;
|
||||
const { reader, read } = factory();
|
||||
reader.releaseLock();
|
||||
|
||||
return Promise.all([
|
||||
promise_rejects_js(t, TypeError, reader.read()),
|
||||
promise_rejects_js(t, TypeError, reader.read())
|
||||
promise_rejects_js(t, TypeError, read()),
|
||||
promise_rejects_js(t, TypeError, read())
|
||||
]);
|
||||
|
||||
}, label + ': releasing the lock should cause further read() calls to reject with a TypeError');
|
||||
|
||||
promise_test(t => {
|
||||
|
||||
const reader = factory().reader;
|
||||
const { reader } = factory();
|
||||
|
||||
const closedBefore = reader.closed;
|
||||
reader.releaseLock();
|
||||
@@ -301,9 +299,7 @@ self.templatedRSEmptyReader = (label, factory) => {
|
||||
|
||||
test(() => {
|
||||
|
||||
const streamAndReader = factory();
|
||||
const stream = streamAndReader.stream;
|
||||
const reader = streamAndReader.reader;
|
||||
const { stream, reader } = factory();
|
||||
|
||||
reader.releaseLock();
|
||||
assert_false(stream.locked, 'locked getter should return false');
|
||||
@@ -312,10 +308,10 @@ self.templatedRSEmptyReader = (label, factory) => {
|
||||
|
||||
promise_test(() => {
|
||||
|
||||
const reader = factory().reader;
|
||||
const { reader, read } = factory();
|
||||
reader.cancel();
|
||||
|
||||
return reader.read().then(r => {
|
||||
return read().then(r => {
|
||||
assert_object_equals(r, { value: undefined, done: true }, 'read()ing from the reader should give a done result');
|
||||
});
|
||||
|
||||
@@ -323,7 +319,7 @@ self.templatedRSEmptyReader = (label, factory) => {
|
||||
|
||||
promise_test(t => {
|
||||
|
||||
const stream = factory().stream;
|
||||
const { stream } = factory();
|
||||
return promise_rejects_js(t, TypeError, stream.cancel());
|
||||
|
||||
}, label + ': canceling via the stream should fail');
|
||||
@@ -719,3 +715,62 @@ self.templatedRSTeeCancel = (label, factory) => {
|
||||
}, `${label}: erroring a teed stream should properly handle canceled branches`);
|
||||
|
||||
};
|
||||
|
||||
self.templatedRSThrowAfterCloseOrError = (label, factory) => {
|
||||
test(() => {}, 'Running templatedRSThrowAfterCloseOrError with ' + label);
|
||||
|
||||
const theError = new Error('a unique string');
|
||||
|
||||
promise_test(async t => {
|
||||
let controller;
|
||||
const stream = factory({
|
||||
start: t.step_func((c) => {
|
||||
controller = c;
|
||||
})
|
||||
});
|
||||
|
||||
controller.close();
|
||||
|
||||
assert_throws_js(TypeError, () => controller.enqueue(new Uint8Array([1])));
|
||||
}, `${label}: enqueue() throws after close()`);
|
||||
|
||||
promise_test(async t => {
|
||||
let controller;
|
||||
const stream = factory({
|
||||
start: t.step_func((c) => {
|
||||
controller = c;
|
||||
})
|
||||
});
|
||||
|
||||
controller.enqueue(new Uint8Array([1]));
|
||||
controller.close();
|
||||
|
||||
assert_throws_js(TypeError, () => controller.enqueue(new Uint8Array([2])));
|
||||
}, `${label}: enqueue() throws after enqueue() and close()`);
|
||||
|
||||
promise_test(async t => {
|
||||
let controller;
|
||||
const stream = factory({
|
||||
start: t.step_func((c) => {
|
||||
controller = c;
|
||||
})
|
||||
});
|
||||
|
||||
controller.error(theError);
|
||||
|
||||
assert_throws_js(TypeError, () => controller.enqueue(new Uint8Array([1])));
|
||||
}, `${label}: enqueue() throws after error()`);
|
||||
|
||||
promise_test(async t => {
|
||||
let controller;
|
||||
const stream = factory({
|
||||
start: t.step_func((c) => {
|
||||
controller = c;
|
||||
})
|
||||
});
|
||||
|
||||
controller.error(theError);
|
||||
|
||||
assert_throws_js(TypeError, () => controller.close());
|
||||
}, `${label}: close() throws after error()`);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
features:
|
||||
- name: transferable-streams
|
||||
files: "**"
|
||||
@@ -0,0 +1,8 @@
|
||||
features:
|
||||
- name: streams
|
||||
files:
|
||||
- "*"
|
||||
- "!cancel.any.js"
|
||||
- name: transformstream-transformer-cancel
|
||||
files:
|
||||
- cancel.any.js
|
||||
@@ -113,3 +113,93 @@ promise_test(async t => {
|
||||
const closePromise = ts.readable.cancel(1);
|
||||
await promise_rejects_exactly(t, thrownError, closePromise, 'closePromise should reject with thrownError');
|
||||
}, 'writable.abort() and readable.cancel() should reject if a transformer.cancel() calls controller.error()');
|
||||
|
||||
promise_test(async t => {
|
||||
const cancelReason = new Error('cancel reason');
|
||||
let controller;
|
||||
let cancelPromise;
|
||||
let flushCalled = false;
|
||||
const ts = new TransformStream({
|
||||
start(c) {
|
||||
controller = c;
|
||||
},
|
||||
flush() {
|
||||
flushCalled = true;
|
||||
cancelPromise = ts.readable.cancel(cancelReason);
|
||||
},
|
||||
cancel: t.unreached_func('cancel should not be called')
|
||||
});
|
||||
await flushAsyncEvents(); // ensure stream is started
|
||||
await ts.writable.close();
|
||||
assert_true(flushCalled, 'flush() was called');
|
||||
await cancelPromise;
|
||||
}, 'readable.cancel() should not call cancel() when flush() is already called from writable.close()');
|
||||
|
||||
promise_test(async t => {
|
||||
const cancelReason = new Error('cancel reason');
|
||||
const abortReason = new Error('abort reason');
|
||||
let cancelCalls = 0;
|
||||
let controller;
|
||||
let cancelPromise;
|
||||
const ts = new TransformStream({
|
||||
start(c) {
|
||||
controller = c;
|
||||
},
|
||||
cancel() {
|
||||
if (++cancelCalls === 1) {
|
||||
cancelPromise = ts.readable.cancel(cancelReason);
|
||||
}
|
||||
},
|
||||
flush: t.unreached_func('flush should not be called')
|
||||
});
|
||||
await flushAsyncEvents(); // ensure stream is started
|
||||
await ts.writable.abort(abortReason);
|
||||
assert_equals(cancelCalls, 1);
|
||||
await cancelPromise;
|
||||
assert_equals(cancelCalls, 1);
|
||||
}, 'readable.cancel() should not call cancel() again when already called from writable.abort()');
|
||||
|
||||
promise_test(async t => {
|
||||
const cancelReason = new Error('cancel reason');
|
||||
let controller;
|
||||
let closePromise;
|
||||
let cancelCalled = false;
|
||||
const ts = new TransformStream({
|
||||
start(c) {
|
||||
controller = c;
|
||||
},
|
||||
cancel() {
|
||||
cancelCalled = true;
|
||||
closePromise = ts.writable.close();
|
||||
},
|
||||
flush: t.unreached_func('flush should not be called')
|
||||
});
|
||||
await flushAsyncEvents(); // ensure stream is started
|
||||
await ts.readable.cancel(cancelReason);
|
||||
assert_true(cancelCalled, 'cancel() was called');
|
||||
await closePromise;
|
||||
}, 'writable.close() should not call flush() when cancel() is already called from readable.cancel()');
|
||||
|
||||
promise_test(async t => {
|
||||
const cancelReason = new Error('cancel reason');
|
||||
const abortReason = new Error('abort reason');
|
||||
let cancelCalls = 0;
|
||||
let controller;
|
||||
let abortPromise;
|
||||
const ts = new TransformStream({
|
||||
start(c) {
|
||||
controller = c;
|
||||
},
|
||||
cancel() {
|
||||
if (++cancelCalls === 1) {
|
||||
abortPromise = ts.writable.abort(abortReason);
|
||||
}
|
||||
},
|
||||
flush: t.unreached_func('flush should not be called')
|
||||
});
|
||||
await flushAsyncEvents(); // ensure stream is started
|
||||
await promise_rejects_exactly(t, abortReason, ts.readable.cancel(cancelReason));
|
||||
assert_equals(cancelCalls, 1);
|
||||
await promise_rejects_exactly(t, abortReason, abortPromise);
|
||||
assert_equals(cancelCalls, 1);
|
||||
}, 'writable.abort() should not call cancel() again when already called from readable.cancel()');
|
||||
@@ -0,0 +1,3 @@
|
||||
features:
|
||||
- name: streams
|
||||
files: "**"
|
||||
+84
-4
@@ -1472,6 +1472,8 @@ promise_test(async t => {
|
||||
|
||||
promise_test(async t => {
|
||||
let ctrl;
|
||||
let abortPromise;
|
||||
let abortPromiseFromSignal;
|
||||
const e1 = SyntaxError();
|
||||
const e2 = TypeError();
|
||||
const ws = new WritableStream({
|
||||
@@ -1479,9 +1481,87 @@ promise_test(async t => {
|
||||
});
|
||||
|
||||
const writer = ws.getWriter();
|
||||
ctrl.signal.addEventListener('abort', () => writer.abort(e2));
|
||||
writer.abort(e1);
|
||||
ctrl.signal.addEventListener('abort', () => {
|
||||
abortPromiseFromSignal = writer.abort(e2);
|
||||
});
|
||||
abortPromise = writer.abort(e1);
|
||||
assert_true(ctrl.signal.aborted);
|
||||
|
||||
await promise_rejects_exactly(t, e2, writer.closed, 'closed');
|
||||
}, 'recursive abort() call');
|
||||
await Promise.all([
|
||||
abortPromise,
|
||||
abortPromiseFromSignal,
|
||||
promise_rejects_exactly(t, e2, writer.closed, 'closed')
|
||||
]);
|
||||
}, 'recursive abort() call from abort() aborting signal (not started)');
|
||||
|
||||
promise_test(async t => {
|
||||
let ctrl;
|
||||
let abortPromise;
|
||||
let abortPromiseFromSignal;
|
||||
const e1 = SyntaxError();
|
||||
const e2 = TypeError();
|
||||
const ws = new WritableStream({
|
||||
start(c) { ctrl = c; },
|
||||
});
|
||||
await flushAsyncEvents(); // ensure stream is started
|
||||
|
||||
const writer = ws.getWriter();
|
||||
ctrl.signal.addEventListener('abort', () => {
|
||||
abortPromiseFromSignal = writer.abort(e2);
|
||||
});
|
||||
abortPromise = writer.abort(e1);
|
||||
assert_true(ctrl.signal.aborted);
|
||||
|
||||
await Promise.all([
|
||||
abortPromise,
|
||||
abortPromiseFromSignal,
|
||||
promise_rejects_exactly(t, e2, writer.closed, 'closed')
|
||||
]);
|
||||
}, 'recursive abort() call from abort() aborting signal');
|
||||
|
||||
promise_test(async t => {
|
||||
let ctrl;
|
||||
let abortPromise;
|
||||
let closePromiseFromSignal;
|
||||
const theError = SyntaxError();
|
||||
const ws = new WritableStream({
|
||||
start(c) { ctrl = c; },
|
||||
});
|
||||
|
||||
const writer = ws.getWriter();
|
||||
ctrl.signal.addEventListener('abort', () => {
|
||||
closePromiseFromSignal = writer.close();
|
||||
});
|
||||
abortPromise = writer.abort(theError);
|
||||
assert_true(ctrl.signal.aborted);
|
||||
|
||||
await Promise.all([
|
||||
abortPromise,
|
||||
promise_rejects_exactly(t, theError, closePromiseFromSignal, 'closed'),
|
||||
promise_rejects_exactly(t, theError, writer.closed, 'closed')
|
||||
]);
|
||||
}, 'recursive close() call from abort() aborting signal (not started)');
|
||||
|
||||
promise_test(async t => {
|
||||
let ctrl;
|
||||
let abortPromise;
|
||||
let closePromiseFromSignal;
|
||||
const theError = SyntaxError();
|
||||
const ws = new WritableStream({
|
||||
start(c) { ctrl = c; },
|
||||
});
|
||||
await flushAsyncEvents(); // ensure stream is started
|
||||
|
||||
const writer = ws.getWriter();
|
||||
ctrl.signal.addEventListener('abort', () => {
|
||||
closePromiseFromSignal = writer.close();
|
||||
});
|
||||
abortPromise = writer.abort(theError);
|
||||
assert_true(ctrl.signal.aborted);
|
||||
|
||||
await Promise.all([
|
||||
abortPromise,
|
||||
closePromiseFromSignal,
|
||||
writer.closed
|
||||
]);
|
||||
}, 'recursive close() call from abort() aborting signal');
|
||||
@@ -468,3 +468,14 @@ promise_test(t => {
|
||||
|
||||
return promise_rejects_js(t, TypeError, ws.close(), 'close should reject');
|
||||
}, 'close() on a stream with a pending close should reject');
|
||||
|
||||
// See https://github.com/whatwg/streams/issues/1341.
|
||||
promise_test(async t => {
|
||||
const ws = new WritableStream();
|
||||
const writer = ws.getWriter();
|
||||
|
||||
await writer.write(1);
|
||||
await writer.close();
|
||||
|
||||
return promise_rejects_js(t, TypeError, writer.write(2), 'write should reject');
|
||||
}, 'write() on a closed stream should reject');
|
||||
@@ -87,6 +87,10 @@ test(() => {
|
||||
new WritableStream();
|
||||
}, 'WritableStream should be constructible with no arguments');
|
||||
|
||||
test(() => {
|
||||
assert_throws_js(RangeError, () => new WritableStream({ type: 'bytes' }), 'constructor should throw');
|
||||
}, `WritableStream can't be constructed with a defined type`);
|
||||
|
||||
test(() => {
|
||||
const underlyingSink = { get start() { throw error1; } };
|
||||
const queuingStrategy = { highWaterMark: 0, get size() { throw error2; } };
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// META: global=window,worker
|
||||
// META: script=/common/gc.js
|
||||
'use strict';
|
||||
|
||||
// See https://crbug.com/390646657 for details.
|
||||
promise_test(async () => {
|
||||
const written = new WritableStream({
|
||||
write(chunk) {
|
||||
return new Promise(resolve => {});
|
||||
}
|
||||
}).getWriter().write('just nod if you can hear me');
|
||||
for (let i = 0; i < 5; ++i)
|
||||
await garbageCollect();
|
||||
}, 'Garbage-collecting a stream writer with a pending write should not crash');
|
||||
|
||||
promise_test(async () => {
|
||||
const closed = new WritableStream({
|
||||
write(chunk) { }
|
||||
}).getWriter().closed;
|
||||
for (let i = 0; i < 5; ++i)
|
||||
await garbageCollect();
|
||||
}, 'Garbage-collecting a stream writer should not crash with closed promise is retained');
|
||||
|
||||
promise_test(async () => {
|
||||
let writer = new WritableStream({
|
||||
write(chunk) { return new Promise(resolve => {}); },
|
||||
close() { return new Promise(resolve => {}); }
|
||||
}).getWriter();
|
||||
writer.write('is there anyone home?');
|
||||
writer.close();
|
||||
writer = null;
|
||||
for (let i = 0; i < 5; ++i)
|
||||
await garbageCollect();
|
||||
}, 'Garbage-collecting a stream writer should not crash with close promise pending');
|
||||
|
||||
promise_test(async () => {
|
||||
const ready = new WritableStream({
|
||||
write(chunk) { }
|
||||
}, {highWaterMark: 0}).getWriter().ready;
|
||||
for (let i = 0; i < 5; ++i)
|
||||
await garbageCollect();
|
||||
}, 'Garbage-collecting a stream writer should not crash when backpressure is being applied');
|
||||
|
||||
// Repro for https://crbug.com/455800266
|
||||
promise_test(async () => {
|
||||
// This logic is wrapped in a function to make it easy to garbage collect all
|
||||
// references to the WritableStream.
|
||||
const createWritableStream = async () => {
|
||||
const WRITE_COUNT = 2;
|
||||
let writes_done = 0;
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
|
||||
const ws = new WritableStream({
|
||||
write() {
|
||||
if (writes_done === WRITE_COUNT) {
|
||||
// Will never resolve, leaving the write operation pending.
|
||||
return new Promise(resolve => { });
|
||||
}
|
||||
++writes_done;
|
||||
return promise;
|
||||
}
|
||||
});
|
||||
|
||||
const writer = ws.getWriter();
|
||||
await writer.ready;
|
||||
|
||||
const writeChunks = () => {
|
||||
for (let i = 0; i < WRITE_COUNT; ++i) {
|
||||
const ready = writer.ready;
|
||||
writer.write("chunk");
|
||||
}
|
||||
};
|
||||
|
||||
// Apply backpressure.
|
||||
writeChunks();
|
||||
|
||||
// Release backpressure.
|
||||
resolve();
|
||||
await writer.ready;
|
||||
|
||||
// Apply backpressure again.
|
||||
writeChunks();
|
||||
};
|
||||
|
||||
await createWritableStream();
|
||||
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
await garbageCollect();
|
||||
}
|
||||
}, "WritableStream should not crash when garbage collected with backpressure");
|
||||
@@ -0,0 +1,21 @@
|
||||
// META: global=window,worker,shadowrealm
|
||||
// META: script=/common/gc.js
|
||||
'use strict';
|
||||
|
||||
promise_test(async () => {
|
||||
|
||||
let written = false;
|
||||
const promise = (() => {
|
||||
const rs = new WritableStream({
|
||||
write() {
|
||||
written = true;
|
||||
}
|
||||
});
|
||||
const writer = rs.getWriter();
|
||||
return writer.write('something');
|
||||
})();
|
||||
await garbageCollect();
|
||||
await promise;
|
||||
assert_true(written);
|
||||
|
||||
}, 'A WritableStream and its writer should not be garbage collected while there is a write promise pending');
|
||||
Vendored
+1
-1
@@ -68,7 +68,7 @@
|
||||
"path": "resources"
|
||||
},
|
||||
"streams": {
|
||||
"commit": "bc9dcbbf1a4c2c741ef47f47d6ede6458f40c4a4",
|
||||
"commit": "f8f26a372f357370846226ff64db1f9cc98a1edb",
|
||||
"path": "streams"
|
||||
},
|
||||
"url": {
|
||||
|
||||
@@ -8,6 +8,15 @@
|
||||
"readable-streams/cross-realm-crash.window.js": {
|
||||
"skip": "Browser-specific test"
|
||||
},
|
||||
"readable-streams/from.any.js": {
|
||||
"fail": {
|
||||
"note": "does not synchronously validate that the value returned by @@iterator/@@asyncIterator is an object",
|
||||
"expected": [
|
||||
"ReadableStream.from throws on invalid iterables; specifically an object with an @@iterator method returning a non-object",
|
||||
"ReadableStream.from throws on invalid iterables; specifically an object with an @@asyncIterator method returning a non-object"
|
||||
]
|
||||
}
|
||||
},
|
||||
"readable-streams/owning-type-message-port.any.js": {
|
||||
"fail": {
|
||||
"note": "Readable streams with type owning are not yet supported",
|
||||
@@ -43,5 +52,13 @@
|
||||
},
|
||||
"transform-streams/invalid-realm.tentative.window.js": {
|
||||
"skip": "Browser-specific test"
|
||||
},
|
||||
"writable-streams/aborting.any.js": {
|
||||
"fail": {
|
||||
"note": "Recursive abort() call from within an abort algorithm triggers ERR_INTERNAL_ASSERTION",
|
||||
"expected": [
|
||||
"recursive abort() call from abort() aborting signal"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user