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:
Node.js GitHub Bot authored and GitHub committed 2026-04-23 12:15:56 +00:00
1 parent d44a71a251
commit 117892603c
28 files changed
+820 -45

No files matched your search

+1 -1
View File
@@ -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
+1 -2
View File
@@ -1,7 +1,6 @@
spec: https://streams.spec.whatwg.org/
suggested_reviewers:
- domenic
- yutakahirano
- youennf
- wanderview
- ricea
- MattiasBuelens
+5
View File
@@ -0,0 +1,5 @@
features:
- name: streams
files:
# Top-level only. Subdirectories have their own mapping.
- "*"
+3
View File
@@ -0,0 +1,3 @@
features:
- name: streams
files: "**"
@@ -0,0 +1,3 @@
features:
- name: readable-byte-streams
files: "**"
@@ -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');
@@ -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
@@ -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>
@@ -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');
+151
View File
@@ -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
View File
@@ -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
View File
@@ -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');
+11
View File
@@ -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; } };
@@ -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');
+1 -1
View File
@@ -68,7 +68,7 @@
"path": "resources"
},
"streams": {
"commit": "bc9dcbbf1a4c2c741ef47f47d6ede6458f40c4a4",
"commit": "f8f26a372f357370846226ff64db1f9cc98a1edb",
"path": "streams"
},
"url": {
+17
View File
@@ -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"
]
}
}
}