Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,95 @@
// META: global=window,worker,shadowrealm
'use strict';
const error1 = new Error('a unique string');
error1.name = 'error1';
test(() => {
assert_throws_exactly(error1, () => {
new WritableStream({}, {
get size() {
throw error1;
},
highWaterMark: 5
});
}, 'construction should re-throw the error');
}, 'Writable stream: throwing strategy.size getter');
test(() => {
assert_throws_js(TypeError, () => {
new WritableStream({}, { size: 'a string' });
});
}, 'reject any non-function value for strategy.size');
test(() => {
assert_throws_exactly(error1, () => {
new WritableStream({}, {
size() {
return 1;
},
get highWaterMark() {
throw error1;
}
});
}, 'construction should re-throw the error');
}, 'Writable stream: throwing strategy.highWaterMark getter');
test(() => {
for (const highWaterMark of [-1, -Infinity, NaN, 'foo', {}]) {
assert_throws_js(RangeError, () => {
new WritableStream({}, {
size() {
return 1;
},
highWaterMark
});
}, `construction should throw a RangeError for ${highWaterMark}`);
}
}, 'Writable stream: invalid strategy.highWaterMark');
promise_test(t => {
const ws = new WritableStream({}, {
size() {
throw error1;
},
highWaterMark: 5
});
const writer = ws.getWriter();
const p1 = promise_rejects_exactly(t, error1, writer.write('a'), 'write should reject with the thrown error');
const p2 = promise_rejects_exactly(t, error1, writer.closed, 'closed should reject with the thrown error');
return Promise.all([p1, p2]);
}, 'Writable stream: throwing strategy.size method');
promise_test(() => {
const sizes = [NaN, -Infinity, Infinity, -1];
return Promise.all(sizes.map(size => {
const ws = new WritableStream({}, {
size() {
return size;
},
highWaterMark: 5
});
const writer = ws.getWriter();
return writer.write('a').then(() => assert_unreached('write must reject'), writeE => {
assert_equals(writeE.name, 'RangeError', `write must reject with a RangeError for ${size}`);
return writer.closed.then(() => assert_unreached('write must reject'), closedE => {
assert_equals(closedE, writeE, `closed should reject with the same error as write`);
});
});
}));
}, 'Writable stream: invalid strategy.size return value');
test(() => {
assert_throws_js(TypeError, () => new WritableStream(undefined, {
size: 'not a function',
highWaterMark: NaN
}), 'WritableStream constructor should throw a TypeError');
}, 'Writable stream: invalid size beats invalid highWaterMark');

View File

@ -0,0 +1,204 @@
// META: global=window,worker,shadowrealm
// META: script=../resources/test-utils.js
// META: script=../resources/recording-streams.js
'use strict';
const error1 = new Error('error1');
error1.name = 'error1';
test(() => {
assert_throws_exactly(error1, () => {
new WritableStream({
get start() {
throw error1;
}
});
}, 'constructor should throw same error as throwing start getter');
assert_throws_exactly(error1, () => {
new WritableStream({
start() {
throw error1;
}
});
}, 'constructor should throw same error as throwing start method');
assert_throws_js(TypeError, () => {
new WritableStream({
start: 'not a function or undefined'
});
}, 'constructor should throw TypeError when passed a non-function start property');
assert_throws_js(TypeError, () => {
new WritableStream({
start: { apply() {} }
});
}, 'constructor should throw TypeError when passed a non-function start property with an .apply method');
}, 'start: errors in start cause WritableStream constructor to throw');
promise_test(t => {
const ws = recordingWritableStream({
close() {
throw error1;
}
});
const writer = ws.getWriter();
return promise_rejects_exactly(t, error1, writer.close(), 'close() promise must reject with the thrown error')
.then(() => promise_rejects_exactly(t, error1, writer.ready, 'ready promise must reject with the thrown error'))
.then(() => promise_rejects_exactly(t, error1, writer.closed, 'closed promise must reject with the thrown error'))
.then(() => {
assert_array_equals(ws.events, ['close']);
});
}, 'close: throwing method should cause writer close() and ready to reject');
promise_test(t => {
const ws = recordingWritableStream({
close() {
return Promise.reject(error1);
}
});
const writer = ws.getWriter();
return promise_rejects_exactly(t, error1, writer.close(), 'close() promise must reject with the same error')
.then(() => promise_rejects_exactly(t, error1, writer.ready, 'ready promise must reject with the same error'))
.then(() => assert_array_equals(ws.events, ['close']));
}, 'close: returning a rejected promise should cause writer close() and ready to reject');
test(() => {
assert_throws_exactly(error1, () => new WritableStream({
get close() {
throw error1;
}
}), 'constructor should throw');
}, 'close: throwing getter should cause constructor to throw');
test(() => {
assert_throws_exactly(error1, () => new WritableStream({
get write() {
throw error1;
}
}), 'constructor should throw');
}, 'write: throwing getter should cause write() and closed to reject');
promise_test(t => {
const ws = new WritableStream({
write() {
throw error1;
}
});
const writer = ws.getWriter();
return promise_rejects_exactly(t, error1, writer.write('a'), 'write should reject with the thrown error')
.then(() => promise_rejects_exactly(t, error1, writer.closed, 'closed should reject with the thrown error'));
}, 'write: throwing method should cause write() and closed to reject');
promise_test(t => {
let rejectSinkWritePromise;
const ws = recordingWritableStream({
write() {
return new Promise((r, reject) => {
rejectSinkWritePromise = reject;
});
}
});
return flushAsyncEvents().then(() => {
const writer = ws.getWriter();
const writePromise = writer.write('a');
rejectSinkWritePromise(error1);
return Promise.all([
promise_rejects_exactly(t, error1, writePromise, 'writer write must reject with the same error'),
promise_rejects_exactly(t, error1, writer.ready, 'ready promise must reject with the same error')
]);
})
.then(() => {
assert_array_equals(ws.events, ['write', 'a']);
});
}, 'write: returning a promise that becomes rejected after the writer write() should cause writer write() and ready ' +
'to reject');
promise_test(t => {
const ws = recordingWritableStream({
write() {
if (ws.events.length === 2) {
return delay(0);
}
return Promise.reject(error1);
}
});
const writer = ws.getWriter();
// Do not wait for this; we want to test the ready promise when the stream is "full" (desiredSize = 0), but if we wait
// then the stream will transition back to "empty" (desiredSize = 1)
writer.write('a');
const readyPromise = writer.ready;
return promise_rejects_exactly(t, error1, writer.write('b'), 'second write must reject with the same error').then(() => {
assert_equals(writer.ready, readyPromise,
'the ready promise must not change, since the queue was full after the first write, so the pending one simply ' +
'transitioned');
return promise_rejects_exactly(t, error1, writer.ready, 'ready promise must reject with the same error');
})
.then(() => assert_array_equals(ws.events, ['write', 'a', 'write', 'b']));
}, 'write: returning a rejected promise (second write) should cause writer write() and ready to reject');
test(() => {
assert_throws_js(TypeError, () => new WritableStream({
start: 'test'
}), 'constructor should throw');
}, 'start: non-function start method');
test(() => {
assert_throws_js(TypeError, () => new WritableStream({
write: 'test'
}), 'constructor should throw');
}, 'write: non-function write method');
test(() => {
assert_throws_js(TypeError, () => new WritableStream({
close: 'test'
}), 'constructor should throw');
}, 'close: non-function close method');
test(() => {
assert_throws_js(TypeError, () => new WritableStream({
abort: { apply() {} }
}), 'constructor should throw');
}, 'abort: non-function abort method with .apply');
test(() => {
assert_throws_exactly(error1, () => new WritableStream({
get abort() {
throw error1;
}
}), 'constructor should throw');
}, 'abort: throwing getter should cause abort() and closed to reject');
promise_test(t => {
const abortReason = new Error('different string');
const ws = new WritableStream({
abort() {
throw error1;
}
});
const writer = ws.getWriter();
return promise_rejects_exactly(t, error1, writer.abort(abortReason), 'abort should reject with the thrown error')
.then(() => promise_rejects_exactly(t, abortReason, writer.closed, 'closed should reject with abortReason'));
}, 'abort: throwing method should cause abort() and closed to reject');

View File

@ -0,0 +1,28 @@
// META: global=window,worker,shadowrealm
'use strict';
promise_test(t => {
let isDone = false;
const ws = new WritableStream(
{
write() {
return new Promise(resolve => {
t.step_timeout(() => {
isDone = true;
resolve();
}, 200);
});
},
close() {
assert_true(isDone, 'close is only called once the promise has been resolved');
}
},
new ByteLengthQueuingStrategy({ highWaterMark: 1024 * 16 })
);
const writer = ws.getWriter();
writer.write({ byteLength: 1024 });
return writer.close();
}, 'Closing a writable stream with in-flight writes below the high water mark delays the close call properly');

View File

@ -0,0 +1,470 @@
// META: global=window,worker,shadowrealm
// META: script=../resources/test-utils.js
// META: script=../resources/recording-streams.js
'use strict';
const error1 = new Error('error1');
error1.name = 'error1';
const error2 = new Error('error2');
error2.name = 'error2';
promise_test(() => {
const ws = new WritableStream({
close() {
return 'Hello';
}
});
const writer = ws.getWriter();
const closePromise = writer.close();
return closePromise.then(value => assert_equals(value, undefined, 'fulfillment value must be undefined'));
}, 'fulfillment value of writer.close() call must be undefined even if the underlying sink returns a non-undefined ' +
'value');
promise_test(() => {
let controller;
let resolveClose;
const ws = new WritableStream({
start(c) {
controller = c;
},
close() {
return new Promise(resolve => {
resolveClose = resolve;
});
}
});
const writer = ws.getWriter();
const closePromise = writer.close();
return flushAsyncEvents().then(() => {
controller.error(error1);
return flushAsyncEvents();
}).then(() => {
resolveClose();
return Promise.all([
closePromise,
writer.closed,
flushAsyncEvents().then(() => writer.closed)]);
});
}, 'when sink calls error asynchronously while sink close is in-flight, the stream should not become errored');
promise_test(() => {
let controller;
const passedError = new Error('error me');
const ws = new WritableStream({
start(c) {
controller = c;
},
close() {
controller.error(passedError);
}
});
const writer = ws.getWriter();
return writer.close().then(() => writer.closed);
}, 'when sink calls error synchronously while closing, the stream should not become errored');
promise_test(t => {
const ws = new WritableStream({
close() {
throw error1;
}
});
const writer = ws.getWriter();
return Promise.all([
writer.write('y'),
promise_rejects_exactly(t, error1, writer.close(), 'close() must reject with the error'),
promise_rejects_exactly(t, error1, writer.closed, 'closed must reject with the error')
]);
}, 'when the sink throws during close, and the close is requested while a write is still in-flight, the stream should ' +
'become errored during the close');
promise_test(() => {
const ws = new WritableStream({
write(chunk, controller) {
controller.error(error1);
return new Promise(() => {});
}
});
const writer = ws.getWriter();
writer.write('a');
return delay(0).then(() => {
writer.releaseLock();
});
}, 'releaseLock on a stream with a pending write in which the stream has been errored');
promise_test(() => {
let controller;
const ws = new WritableStream({
start(c) {
controller = c;
},
close() {
controller.error(error1);
return new Promise(() => {});
}
});
const writer = ws.getWriter();
writer.close();
return delay(0).then(() => {
writer.releaseLock();
});
}, 'releaseLock on a stream with a pending close in which controller.error() was called');
promise_test(() => {
const ws = recordingWritableStream();
const writer = ws.getWriter();
return writer.ready.then(() => {
assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
writer.close();
assert_equals(writer.desiredSize, 1, 'desiredSize should be still 1');
return writer.ready.then(v => {
assert_equals(v, undefined, 'ready promise should be fulfilled with undefined');
assert_array_equals(ws.events, ['close'], 'write and abort should not be called');
});
});
}, 'when close is called on a WritableStream in writable state, ready should return a fulfilled promise');
promise_test(() => {
const ws = recordingWritableStream({
write() {
return new Promise(() => {});
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
writer.write('a');
assert_equals(writer.desiredSize, 0, 'desiredSize should be 0');
let calledClose = false;
return Promise.all([
writer.ready.then(v => {
assert_equals(v, undefined, 'ready promise should be fulfilled with undefined');
assert_true(calledClose, 'ready should not be fulfilled before writer.close() is called');
assert_array_equals(ws.events, ['write', 'a'], 'sink abort() should not be called');
}),
flushAsyncEvents().then(() => {
writer.close();
calledClose = true;
})
]);
});
}, 'when close is called on a WritableStream in waiting state, ready promise should be fulfilled');
promise_test(() => {
let asyncCloseFinished = false;
const ws = recordingWritableStream({
close() {
return flushAsyncEvents().then(() => {
asyncCloseFinished = true;
});
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
writer.write('a');
writer.close();
return writer.ready.then(v => {
assert_false(asyncCloseFinished, 'ready promise should be fulfilled before async close completes');
assert_equals(v, undefined, 'ready promise should be fulfilled with undefined');
assert_array_equals(ws.events, ['write', 'a', 'close'], 'sink abort() should not be called');
});
});
}, 'when close is called on a WritableStream in waiting state, ready should be fulfilled immediately even if close ' +
'takes a long time');
promise_test(t => {
const rejection = { name: 'letter' };
const ws = new WritableStream({
close() {
return {
then(onFulfilled, onRejected) { onRejected(rejection); }
};
}
});
return promise_rejects_exactly(t, rejection, ws.getWriter().close(), 'close() should return a rejection');
}, 'returning a thenable from close() should work');
promise_test(t => {
const ws = new WritableStream();
const writer = ws.getWriter();
return writer.ready.then(() => {
const closePromise = writer.close();
const closedPromise = writer.closed;
writer.releaseLock();
return Promise.all([
closePromise,
promise_rejects_js(t, TypeError, closedPromise, '.closed promise should be rejected')
]);
});
}, 'releaseLock() should not change the result of sync close()');
promise_test(t => {
const ws = new WritableStream({
close() {
return flushAsyncEvents();
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
const closePromise = writer.close();
const closedPromise = writer.closed;
writer.releaseLock();
return Promise.all([
closePromise,
promise_rejects_js(t, TypeError, closedPromise, '.closed promise should be rejected')
]);
});
}, 'releaseLock() should not change the result of async close()');
promise_test(() => {
let resolveClose;
const ws = new WritableStream({
close() {
const promise = new Promise(resolve => {
resolveClose = resolve;
});
return promise;
}
});
const writer = ws.getWriter();
const closePromise = writer.close();
writer.releaseLock();
return delay(0).then(() => {
resolveClose();
return closePromise.then(() => {
assert_equals(ws.getWriter().desiredSize, 0, 'desiredSize should be 0');
});
});
}, 'close() should set state to CLOSED even if writer has detached');
promise_test(() => {
let resolveClose;
const ws = new WritableStream({
close() {
const promise = new Promise(resolve => {
resolveClose = resolve;
});
return promise;
}
});
const writer = ws.getWriter();
writer.close();
writer.releaseLock();
return delay(0).then(() => {
const abortingWriter = ws.getWriter();
const abortPromise = abortingWriter.abort();
abortingWriter.releaseLock();
resolveClose();
return abortPromise;
});
}, 'the promise returned by async abort during close should resolve');
// Though the order in which the promises are fulfilled or rejected is arbitrary, we're checking it for
// interoperability. We can change the order as long as we file bugs on all implementers to update to the latest tests
// to keep them interoperable.
promise_test(() => {
const ws = new WritableStream({});
const writer = ws.getWriter();
const closePromise = writer.close();
const events = [];
return Promise.all([
closePromise.then(() => {
events.push('closePromise');
}),
writer.closed.then(() => {
events.push('closed');
})
]).then(() => {
assert_array_equals(events, ['closePromise', 'closed'],
'promises must fulfill/reject in the expected order');
});
}, 'promises must fulfill/reject in the expected order on closure');
promise_test(() => {
const ws = new WritableStream({});
// Wait until the WritableStream starts so that the close() call gets processed. Otherwise, abort() will be
// processed without waiting for completion of the close().
return delay(0).then(() => {
const writer = ws.getWriter();
const closePromise = writer.close();
const abortPromise = writer.abort(error1);
const events = [];
return Promise.all([
closePromise.then(() => {
events.push('closePromise');
}),
abortPromise.then(() => {
events.push('abortPromise');
}),
writer.closed.then(() => {
events.push('closed');
})
]).then(() => {
assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
'promises must fulfill/reject in the expected order');
});
});
}, 'promises must fulfill/reject in the expected order on aborted closure');
promise_test(t => {
const ws = new WritableStream({
close() {
return Promise.reject(error1);
}
});
// Wait until the WritableStream starts so that the close() call gets processed.
return delay(0).then(() => {
const writer = ws.getWriter();
const closePromise = writer.close();
const abortPromise = writer.abort(error2);
const events = [];
closePromise.catch(() => events.push('closePromise'));
abortPromise.catch(() => events.push('abortPromise'));
writer.closed.catch(() => events.push('closed'));
return Promise.all([
promise_rejects_exactly(t, error1, closePromise,
'closePromise must reject with the error returned from the sink\'s close method'),
promise_rejects_exactly(t, error1, abortPromise,
'abortPromise must reject with the error returned from the sink\'s close method'),
promise_rejects_exactly(t, error2, writer.closed,
'writer.closed must reject with error2')
]).then(() => {
assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
'promises must fulfill/reject in the expected order');
});
});
}, 'promises must fulfill/reject in the expected order on aborted and errored closure');
promise_test(t => {
let resolveWrite;
let controller;
const ws = new WritableStream({
write(chunk, c) {
controller = c;
return new Promise(resolve => {
resolveWrite = resolve;
});
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
const writePromise = writer.write('c');
controller.error(error1);
const closePromise = writer.close();
let closeRejected = false;
closePromise.catch(() => {
closeRejected = true;
});
return flushAsyncEvents().then(() => {
assert_false(closeRejected);
resolveWrite();
return Promise.all([
writePromise,
promise_rejects_exactly(t, error1, closePromise, 'close() should reject')
]).then(() => {
assert_true(closeRejected);
});
});
});
}, 'close() should not reject until no sink methods are in flight');
promise_test(() => {
const ws = new WritableStream();
const writer1 = ws.getWriter();
return writer1.close().then(() => {
writer1.releaseLock();
const writer2 = ws.getWriter();
const ready = writer2.ready;
assert_equals(ready.constructor, Promise);
return ready;
});
}, 'ready promise should be initialised as fulfilled for a writer on a closed stream');
promise_test(() => {
const ws = new WritableStream();
ws.close();
const writer = ws.getWriter();
return writer.closed;
}, 'close() on a writable stream should work');
promise_test(t => {
const ws = new WritableStream();
ws.getWriter();
return promise_rejects_js(t, TypeError, ws.close(), 'close should reject');
}, 'close() on a locked stream should reject');
promise_test(t => {
const ws = new WritableStream({
start(controller) {
controller.error(error1);
}
});
return promise_rejects_exactly(t, error1, ws.close(), 'close should reject with error1');
}, 'close() on an erroring stream should reject');
promise_test(t => {
const ws = new WritableStream({
start(controller) {
controller.error(error1);
}
});
const writer = ws.getWriter();
return promise_rejects_exactly(t, error1, writer.closed, 'closed should reject with the error').then(() => {
writer.releaseLock();
return promise_rejects_js(t, TypeError, ws.close(), 'close should reject');
});
}, 'close() on an errored stream should reject');
promise_test(t => {
const ws = new WritableStream();
const writer = ws.getWriter();
return writer.close().then(() => {
return promise_rejects_js(t, TypeError, ws.close(), 'close should reject');
});
}, 'close() on an closed stream should reject');
promise_test(t => {
const ws = new WritableStream({
close() {
return new Promise(() => {});
}
});
const writer = ws.getWriter();
writer.close();
writer.releaseLock();
return promise_rejects_js(t, TypeError, ws.close(), 'close should reject');
}, 'close() on a stream with a pending close should reject');

View File

@ -0,0 +1,155 @@
// META: global=window,worker,shadowrealm
'use strict';
const error1 = new Error('error1');
error1.name = 'error1';
const error2 = new Error('error2');
error2.name = 'error2';
promise_test(() => {
let controller;
const ws = new WritableStream({
start(c) {
controller = c;
}
});
// Now error the stream after its construction.
controller.error(error1);
const writer = ws.getWriter();
assert_equals(writer.desiredSize, null, 'desiredSize should be null');
return writer.closed.catch(r => {
assert_equals(r, error1, 'ws should be errored by the passed error');
});
}, 'controller argument should be passed to start method');
promise_test(t => {
const ws = new WritableStream({
write(chunk, controller) {
controller.error(error1);
}
});
const writer = ws.getWriter();
return Promise.all([
writer.write('a'),
promise_rejects_exactly(t, error1, writer.closed, 'controller.error() in write() should error the stream')
]);
}, 'controller argument should be passed to write method');
// Older versions of the standard had the controller argument passed to close(). It wasn't useful, and so has been
// removed. This test remains to identify implementations that haven't been updated.
promise_test(t => {
const ws = new WritableStream({
close(...args) {
t.step(() => {
assert_array_equals(args, [], 'no arguments should be passed to close');
});
}
});
return ws.getWriter().close();
}, 'controller argument should not be passed to close method');
promise_test(() => {
const ws = new WritableStream({}, {
highWaterMark: 1000,
size() { return 1; }
});
const writer = ws.getWriter();
assert_equals(writer.desiredSize, 1000, 'desiredSize should be 1000');
return writer.ready.then(v => {
assert_equals(v, undefined, 'ready promise should fulfill with undefined');
});
}, 'highWaterMark should be reflected to desiredSize');
promise_test(() => {
const ws = new WritableStream({}, {
highWaterMark: Infinity,
size() { return 0; }
});
const writer = ws.getWriter();
assert_equals(writer.desiredSize, Infinity, 'desiredSize should be Infinity');
return writer.ready;
}, 'WritableStream should be writable and ready should fulfill immediately if the strategy does not apply ' +
'backpressure');
test(() => {
new WritableStream();
}, 'WritableStream should be constructible with no arguments');
test(() => {
const underlyingSink = { get start() { throw error1; } };
const queuingStrategy = { highWaterMark: 0, get size() { throw error2; } };
// underlyingSink is converted in prose in the method body, whereas queuingStrategy is done at the IDL layer.
// So the queuingStrategy exception should be encountered first.
assert_throws_exactly(error2, () => new WritableStream(underlyingSink, queuingStrategy));
}, 'underlyingSink argument should be converted after queuingStrategy argument');
test(() => {
const ws = new WritableStream({});
const writer = ws.getWriter();
assert_equals(typeof writer.write, 'function', 'writer should have a write method');
assert_equals(typeof writer.abort, 'function', 'writer should have an abort method');
assert_equals(typeof writer.close, 'function', 'writer should have a close method');
assert_equals(writer.desiredSize, 1, 'desiredSize should start at 1');
assert_not_equals(typeof writer.ready, 'undefined', 'writer should have a ready property');
assert_equals(typeof writer.ready.then, 'function', 'ready property should be thenable');
assert_not_equals(typeof writer.closed, 'undefined', 'writer should have a closed property');
assert_equals(typeof writer.closed.then, 'function', 'closed property should be thenable');
}, 'WritableStream instances should have standard methods and properties');
test(() => {
let WritableStreamDefaultController;
new WritableStream({
start(c) {
WritableStreamDefaultController = c.constructor;
}
});
assert_throws_js(TypeError, () => new WritableStreamDefaultController({}),
'constructor should throw a TypeError exception');
}, 'WritableStreamDefaultController constructor should throw');
test(() => {
let WritableStreamDefaultController;
const stream = new WritableStream({
start(c) {
WritableStreamDefaultController = c.constructor;
}
});
assert_throws_js(TypeError, () => new WritableStreamDefaultController(stream),
'constructor should throw a TypeError exception');
}, 'WritableStreamDefaultController constructor should throw when passed an initialised WritableStream');
test(() => {
const stream = new WritableStream();
const writer = stream.getWriter();
const WritableStreamDefaultWriter = writer.constructor;
writer.releaseLock();
assert_throws_js(TypeError, () => new WritableStreamDefaultWriter({}),
'constructor should throw a TypeError exception');
}, 'WritableStreamDefaultWriter should throw unless passed a WritableStream');
test(() => {
const stream = new WritableStream();
const writer = stream.getWriter();
const WritableStreamDefaultWriter = writer.constructor;
assert_throws_js(TypeError, () => new WritableStreamDefaultWriter(stream),
'constructor should throw a TypeError exception');
}, 'WritableStreamDefaultWriter constructor should throw when stream argument is locked');

View File

@ -0,0 +1,124 @@
// META: global=window,worker,shadowrealm
'use strict';
test(() => {
new WritableStream({}, new CountQueuingStrategy({ highWaterMark: 4 }));
}, 'Can construct a writable stream with a valid CountQueuingStrategy');
promise_test(() => {
const dones = Object.create(null);
const ws = new WritableStream(
{
write(chunk) {
return new Promise(resolve => {
dones[chunk] = resolve;
});
}
},
new CountQueuingStrategy({ highWaterMark: 0 })
);
const writer = ws.getWriter();
let writePromiseB;
let writePromiseC;
return Promise.resolve().then(() => {
assert_equals(writer.desiredSize, 0, 'desiredSize should be initially 0');
const writePromiseA = writer.write('a');
assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after 1st write()');
writePromiseB = writer.write('b');
assert_equals(writer.desiredSize, -2, 'desiredSize should be -2 after 2nd write()');
dones.a();
return writePromiseA;
}).then(() => {
assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after completing 1st write()');
dones.b();
return writePromiseB;
}).then(() => {
assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after completing 2nd write()');
writePromiseC = writer.write('c');
assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after 3rd write()');
dones.c();
return writePromiseC;
}).then(() => {
assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after completing 3rd write()');
});
}, 'Correctly governs the value of a WritableStream\'s state property (HWM = 0)');
promise_test(() => {
const dones = Object.create(null);
const ws = new WritableStream(
{
write(chunk) {
return new Promise(resolve => {
dones[chunk] = resolve;
});
}
},
new CountQueuingStrategy({ highWaterMark: 4 })
);
const writer = ws.getWriter();
let writePromiseB;
let writePromiseC;
let writePromiseD;
return Promise.resolve().then(() => {
assert_equals(writer.desiredSize, 4, 'desiredSize should be initially 4');
const writePromiseA = writer.write('a');
assert_equals(writer.desiredSize, 3, 'desiredSize should be 3 after 1st write()');
writePromiseB = writer.write('b');
assert_equals(writer.desiredSize, 2, 'desiredSize should be 2 after 2nd write()');
writePromiseC = writer.write('c');
assert_equals(writer.desiredSize, 1, 'desiredSize should be 1 after 3rd write()');
writePromiseD = writer.write('d');
assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after 4th write()');
writer.write('e');
assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after 5th write()');
writer.write('f');
assert_equals(writer.desiredSize, -2, 'desiredSize should be -2 after 6th write()');
writer.write('g');
assert_equals(writer.desiredSize, -3, 'desiredSize should be -3 after 7th write()');
dones.a();
return writePromiseA;
}).then(() => {
assert_equals(writer.desiredSize, -2, 'desiredSize should be -2 after completing 1st write()');
dones.b();
return writePromiseB;
}).then(() => {
assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after completing 2nd write()');
dones.c();
return writePromiseC;
}).then(() => {
assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after completing 3rd write()');
writer.write('h');
assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after 8th write()');
dones.d();
return writePromiseD;
}).then(() => {
assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after completing 4th write()');
writer.write('i');
assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after 9th write()');
});
}, 'Correctly governs the value of a WritableStream\'s state property (HWM = 4)');

View File

@ -0,0 +1,64 @@
// META: global=window,worker,shadowrealm
'use strict';
const error1 = new Error('error1');
error1.name = 'error1';
const error2 = new Error('error2');
error2.name = 'error2';
promise_test(t => {
const ws = new WritableStream({
start(controller) {
controller.error(error1);
}
});
return promise_rejects_exactly(t, error1, ws.getWriter().closed, 'stream should be errored');
}, 'controller.error() should error the stream');
test(() => {
let controller;
const ws = new WritableStream({
start(c) {
controller = c;
}
});
ws.abort();
controller.error(error1);
}, 'controller.error() on erroring stream should not throw');
promise_test(t => {
let controller;
const ws = new WritableStream({
start(c) {
controller = c;
}
});
controller.error(error1);
controller.error(error2);
return promise_rejects_exactly(t, error1, ws.getWriter().closed, 'first controller.error() should win');
}, 'surplus calls to controller.error() should be a no-op');
promise_test(() => {
let controller;
const ws = new WritableStream({
start(c) {
controller = c;
}
});
return ws.abort().then(() => {
controller.error(error1);
});
}, 'controller.error() on errored stream should not throw');
promise_test(() => {
let controller;
const ws = new WritableStream({
start(c) {
controller = c;
}
});
return ws.getWriter().close().then(() => {
controller.error(error1);
});
}, 'controller.error() on closed stream should not throw');

View File

@ -0,0 +1,87 @@
// META: global=window,worker,shadowrealm
'use strict';
// Due to the limitations of floating-point precision, the calculation of desiredSize sometimes gives different answers
// than adding up the items in the queue would. It is important that implementations give the same result in these edge
// cases so that developers do not come to depend on non-standard behaviour. See
// https://github.com/whatwg/streams/issues/582 and linked issues for further discussion.
promise_test(() => {
const writer = setupTestStream();
const writePromises = [
writer.write(2),
writer.write(Number.MAX_SAFE_INTEGER)
];
assert_equals(writer.desiredSize, 0 - 2 - Number.MAX_SAFE_INTEGER,
'desiredSize must be calculated using double-precision floating-point arithmetic (after writing two chunks)');
return Promise.all(writePromises).then(() => {
assert_equals(writer.desiredSize, 0, '[[queueTotalSize]] must clamp to 0 if it becomes negative');
});
}, 'Floating point arithmetic must manifest near NUMBER.MAX_SAFE_INTEGER (total ends up positive)');
promise_test(() => {
const writer = setupTestStream();
const writePromises = [
writer.write(1e-16),
writer.write(1)
];
assert_equals(writer.desiredSize, 0 - 1e-16 - 1,
'desiredSize must be calculated using double-precision floating-point arithmetic (after writing two chunks)');
return Promise.all(writePromises).then(() => {
assert_equals(writer.desiredSize, 0, '[[queueTotalSize]] must clamp to 0 if it becomes negative');
});
}, 'Floating point arithmetic must manifest near 0 (total ends up positive, but clamped)');
promise_test(() => {
const writer = setupTestStream();
const writePromises = [
writer.write(1e-16),
writer.write(1),
writer.write(2e-16)
];
assert_equals(writer.desiredSize, 0 - 1e-16 - 1 - 2e-16,
'desiredSize must be calculated using double-precision floating-point arithmetic (after writing three chunks)');
return Promise.all(writePromises).then(() => {
assert_equals(writer.desiredSize, 0 - 1e-16 - 1 - 2e-16 + 1e-16 + 1 + 2e-16,
'desiredSize must be calculated using floating-point arithmetic (after the three chunks have finished writing)');
});
}, 'Floating point arithmetic must manifest near 0 (total ends up positive, and not clamped)');
promise_test(() => {
const writer = setupTestStream();
const writePromises = [
writer.write(2e-16),
writer.write(1)
];
assert_equals(writer.desiredSize, 0 - 2e-16 - 1,
'desiredSize must be calculated using double-precision floating-point arithmetic (after writing two chunks)');
return Promise.all(writePromises).then(() => {
assert_equals(writer.desiredSize, 0 - 2e-16 - 1 + 2e-16 + 1,
'desiredSize must be calculated using floating-point arithmetic (after the two chunks have finished writing)');
});
}, 'Floating point arithmetic must manifest near 0 (total ends up zero)');
function setupTestStream() {
const strategy = {
size(x) {
return x;
},
highWaterMark: 0
};
const ws = new WritableStream({}, strategy);
return ws.getWriter();
}

View File

@ -0,0 +1,277 @@
// META: global=window,worker,shadowrealm
'use strict';
test(() => {
const ws = new WritableStream({});
const writer = ws.getWriter();
writer.releaseLock();
assert_throws_js(TypeError, () => writer.desiredSize, 'desiredSize should throw a TypeError');
}, 'desiredSize on a released writer');
test(() => {
const ws = new WritableStream({});
const writer = ws.getWriter();
assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
}, 'desiredSize initial value');
promise_test(() => {
const ws = new WritableStream({});
const writer = ws.getWriter();
writer.close();
return writer.closed.then(() => {
assert_equals(writer.desiredSize, 0, 'desiredSize should be 0');
});
}, 'desiredSize on a writer for a closed stream');
test(() => {
const ws = new WritableStream({
start(c) {
c.error();
}
});
const writer = ws.getWriter();
assert_equals(writer.desiredSize, null, 'desiredSize should be null');
}, 'desiredSize on a writer for an errored stream');
test(() => {
const ws = new WritableStream({});
const writer = ws.getWriter();
writer.close();
writer.releaseLock();
ws.getWriter();
}, 'ws.getWriter() on a closing WritableStream');
promise_test(() => {
const ws = new WritableStream({});
const writer = ws.getWriter();
return writer.close().then(() => {
writer.releaseLock();
ws.getWriter();
});
}, 'ws.getWriter() on a closed WritableStream');
test(() => {
const ws = new WritableStream({});
const writer = ws.getWriter();
writer.abort();
writer.releaseLock();
ws.getWriter();
}, 'ws.getWriter() on an aborted WritableStream');
promise_test(() => {
const ws = new WritableStream({
start(c) {
c.error();
}
});
const writer = ws.getWriter();
return writer.closed.then(
v => assert_unreached('writer.closed fulfilled unexpectedly with: ' + v),
() => {
writer.releaseLock();
ws.getWriter();
}
);
}, 'ws.getWriter() on an errored WritableStream');
promise_test(() => {
const ws = new WritableStream({});
const writer = ws.getWriter();
writer.releaseLock();
return writer.closed.then(
v => assert_unreached('writer.closed fulfilled unexpectedly with: ' + v),
closedRejection => {
assert_equals(closedRejection.name, 'TypeError', 'closed promise should reject with a TypeError');
return writer.ready.then(
v => assert_unreached('writer.ready fulfilled unexpectedly with: ' + v),
readyRejection => assert_equals(readyRejection, closedRejection,
'ready promise should reject with the same error')
);
}
);
}, 'closed and ready on a released writer');
promise_test(t => {
let thisObject = null;
// Calls to Sink methods after the first are implicitly ignored. Only the first value that is passed to the resolver
// is used.
class Sink {
start() {
// Called twice
t.step(() => {
assert_equals(this, thisObject, 'start should be called as a method');
});
}
write() {
t.step(() => {
assert_equals(this, thisObject, 'write should be called as a method');
});
}
close() {
t.step(() => {
assert_equals(this, thisObject, 'close should be called as a method');
});
}
abort() {
t.step(() => {
assert_equals(this, thisObject, 'abort should be called as a method');
});
}
}
const theSink = new Sink();
thisObject = theSink;
const ws = new WritableStream(theSink);
const writer = ws.getWriter();
writer.write('a');
const closePromise = writer.close();
const ws2 = new WritableStream(theSink);
const writer2 = ws2.getWriter();
const abortPromise = writer2.abort();
return Promise.all([
closePromise,
abortPromise
]);
}, 'WritableStream should call underlying sink methods as methods');
promise_test(t => {
function functionWithOverloads() {}
functionWithOverloads.apply = t.unreached_func('apply() should not be called');
functionWithOverloads.call = t.unreached_func('call() should not be called');
const underlyingSink = {
start: functionWithOverloads,
write: functionWithOverloads,
close: functionWithOverloads,
abort: functionWithOverloads
};
// Test start(), write(), close().
const ws1 = new WritableStream(underlyingSink);
const writer1 = ws1.getWriter();
writer1.write('a');
writer1.close();
// Test abort().
const abortError = new Error();
abortError.name = 'abort error';
const ws2 = new WritableStream(underlyingSink);
const writer2 = ws2.getWriter();
writer2.abort(abortError);
// Test abort() with a close underlying sink method present. (Historical; see
// https://github.com/whatwg/streams/issues/620#issuecomment-263483953 for what used to be
// tested here. But more coverage can't hurt.)
const ws3 = new WritableStream({
start: functionWithOverloads,
write: functionWithOverloads,
close: functionWithOverloads
});
const writer3 = ws3.getWriter();
writer3.abort(abortError);
return writer1.closed
.then(() => promise_rejects_exactly(t, abortError, writer2.closed, 'writer2.closed should be rejected'))
.then(() => promise_rejects_exactly(t, abortError, writer3.closed, 'writer3.closed should be rejected'));
}, 'methods should not not have .apply() or .call() called');
promise_test(() => {
const strategy = {
size() {
if (this !== undefined) {
throw new Error('size called as a method');
}
return 1;
}
};
const ws = new WritableStream({}, strategy);
const writer = ws.getWriter();
return writer.write('a');
}, 'WritableStream\'s strategy.size should not be called as a method');
promise_test(() => {
const ws = new WritableStream();
const writer1 = ws.getWriter();
assert_equals(undefined, writer1.releaseLock(), 'releaseLock() should return undefined');
const writer2 = ws.getWriter();
assert_equals(undefined, writer1.releaseLock(), 'no-op releaseLock() should return undefined');
// Calling releaseLock() on writer1 should not interfere with writer2. If it did, then the ready promise would be
// rejected.
return writer2.ready;
}, 'redundant releaseLock() is no-op');
promise_test(() => {
const events = [];
const ws = new WritableStream();
const writer = ws.getWriter();
return writer.ready.then(() => {
// Force the ready promise back to a pending state.
const writerPromise = writer.write('dummy');
const readyPromise = writer.ready.catch(() => events.push('ready'));
const closedPromise = writer.closed.catch(() => events.push('closed'));
writer.releaseLock();
return Promise.all([readyPromise, closedPromise]).then(() => {
assert_array_equals(events, ['ready', 'closed'], 'ready promise should fire before closed promise');
// Stop the writer promise hanging around after the test has finished.
return Promise.all([
writerPromise,
ws.abort()
]);
});
});
}, 'ready promise should fire before closed on releaseLock');
test(() => {
class Subclass extends WritableStream {
extraFunction() {
return true;
}
}
assert_equals(
Object.getPrototypeOf(Subclass.prototype), WritableStream.prototype,
'Subclass.prototype\'s prototype should be WritableStream.prototype');
assert_equals(Object.getPrototypeOf(Subclass), WritableStream,
'Subclass\'s prototype should be WritableStream');
const sub = new Subclass();
assert_true(sub instanceof WritableStream,
'Subclass object should be an instance of WritableStream');
assert_true(sub instanceof Subclass,
'Subclass object should be an instance of Subclass');
const lockedGetter = Object.getOwnPropertyDescriptor(
WritableStream.prototype, 'locked').get;
assert_equals(lockedGetter.call(sub), sub.locked,
'Subclass object should pass brand check');
assert_true(sub.extraFunction(),
'extraFunction() should be present on Subclass object');
}, 'Subclassing WritableStream should work');
test(() => {
const ws = new WritableStream();
assert_false(ws.locked, 'stream should not be locked');
ws.getWriter();
assert_true(ws.locked, 'stream should be locked');
}, 'the locked getter should return true if the stream has a writer');

View File

@ -0,0 +1,53 @@
// META: global=window,worker,shadowrealm
'use strict';
const sinkMethods = {
start: {
length: 1,
trigger: () => Promise.resolve()
},
write: {
length: 2,
trigger: writer => writer.write()
},
close: {
length: 0,
trigger: writer => writer.close()
},
abort: {
length: 1,
trigger: writer => writer.abort()
}
};
for (const method in sinkMethods) {
const { length, trigger } = sinkMethods[method];
// Some semantic tests of how sink methods are called can be found in general.js, as well as in the test files
// specific to each method.
promise_test(() => {
let argCount;
const ws = new WritableStream({
[method](...args) {
argCount = args.length;
}
});
return Promise.resolve(trigger(ws.getWriter())).then(() => {
assert_equals(argCount, length, `${method} should be called with ${length} arguments`);
});
}, `sink method ${method} should be called with the right number of arguments`);
promise_test(() => {
let methodWasCalled = false;
function Sink() {}
Sink.prototype = {
[method]() {
methodWasCalled = true;
}
};
const ws = new WritableStream(new Sink());
return Promise.resolve(trigger(ws.getWriter())).then(() => {
assert_true(methodWasCalled, `${method} should be called`);
});
}, `sink method ${method} should be called even when it's located on the prototype chain`);
}

View File

@ -0,0 +1,174 @@
// META: global=window,worker,shadowrealm
// META: script=../resources/test-utils.js
// META: script=../resources/recording-streams.js
'use strict';
// These tests exercise the pathological case of calling WritableStream* methods from within the strategy.size()
// callback. This is not something any real code should ever do. Failures here indicate subtle deviations from the
// standard that may affect real, non-pathological code.
const error1 = { name: 'error1' };
promise_test(() => {
let writer;
const strategy = {
size(chunk) {
if (chunk > 0) {
writer.write(chunk - 1);
}
return chunk;
}
};
const ws = recordingWritableStream({}, strategy);
writer = ws.getWriter();
return writer.write(2)
.then(() => {
assert_array_equals(ws.events, ['write', 0, 'write', 1, 'write', 2], 'writes should appear in order');
});
}, 'writes should be written in the standard order');
promise_test(() => {
let writer;
const events = [];
const strategy = {
size(chunk) {
events.push('size', chunk);
if (chunk > 0) {
writer.write(chunk - 1)
.then(() => events.push('writer.write done', chunk - 1));
}
return chunk;
}
};
const ws = new WritableStream({
write(chunk) {
events.push('sink.write', chunk);
}
}, strategy);
writer = ws.getWriter();
return writer.write(2)
.then(() => events.push('writer.write done', 2))
.then(() => flushAsyncEvents())
.then(() => {
assert_array_equals(events, ['size', 2, 'size', 1, 'size', 0,
'sink.write', 0, 'sink.write', 1, 'writer.write done', 0,
'sink.write', 2, 'writer.write done', 1,
'writer.write done', 2],
'events should happen in standard order');
});
}, 'writer.write() promises should resolve in the standard order');
promise_test(t => {
let controller;
const strategy = {
size() {
controller.error(error1);
return 1;
}
};
const ws = recordingWritableStream({
start(c) {
controller = c;
}
}, strategy);
const resolved = [];
const writer = ws.getWriter();
const readyPromise1 = writer.ready.then(() => resolved.push('ready1'));
const writePromise = promise_rejects_exactly(t, error1, writer.write(),
'write() should reject with the error')
.then(() => resolved.push('write'));
const readyPromise2 = promise_rejects_exactly(t, error1, writer.ready, 'ready should reject with error1')
.then(() => resolved.push('ready2'));
const closedPromise = promise_rejects_exactly(t, error1, writer.closed, 'closed should reject with error1')
.then(() => resolved.push('closed'));
return Promise.all([readyPromise1, writePromise, readyPromise2, closedPromise])
.then(() => {
assert_array_equals(resolved, ['ready1', 'write', 'ready2', 'closed'],
'promises should resolve in standard order');
assert_array_equals(ws.events, [], 'underlying sink write should not be called');
});
}, 'controller.error() should work when called from within strategy.size()');
promise_test(t => {
let writer;
const strategy = {
size() {
writer.close();
return 1;
}
};
const ws = recordingWritableStream({}, strategy);
writer = ws.getWriter();
return promise_rejects_js(t, TypeError, writer.write('a'), 'write() promise should reject')
.then(() => {
assert_array_equals(ws.events, ['close'], 'sink.write() should not be called');
});
}, 'close() should work when called from within strategy.size()');
promise_test(t => {
let writer;
const strategy = {
size() {
writer.abort(error1);
return 1;
}
};
const ws = recordingWritableStream({}, strategy);
writer = ws.getWriter();
return promise_rejects_exactly(t, error1, writer.write('a'), 'write() promise should reject')
.then(() => {
assert_array_equals(ws.events, ['abort', error1], 'sink.write() should not be called');
});
}, 'abort() should work when called from within strategy.size()');
promise_test(t => {
let writer;
const strategy = {
size() {
writer.releaseLock();
return 1;
}
};
const ws = recordingWritableStream({}, strategy);
writer = ws.getWriter();
const writePromise = promise_rejects_js(t, TypeError, writer.write('a'), 'write() promise should reject');
const readyPromise = promise_rejects_js(t, TypeError, writer.ready, 'ready promise should reject');
const closedPromise = promise_rejects_js(t, TypeError, writer.closed, 'closed promise should reject');
return Promise.all([writePromise, readyPromise, closedPromise])
.then(() => {
assert_array_equals(ws.events, [], 'sink.write() should not be called');
});
}, 'releaseLock() should abort the write() when called within strategy.size()');
promise_test(t => {
let writer1;
let ws;
let writePromise2;
let closePromise;
let closedPromise2;
const strategy = {
size(chunk) {
if (chunk > 0) {
writer1.releaseLock();
const writer2 = ws.getWriter();
writePromise2 = writer2.write(0);
closePromise = writer2.close();
closedPromise2 = writer2.closed;
}
return 1;
}
};
ws = recordingWritableStream({}, strategy);
writer1 = ws.getWriter();
const writePromise1 = promise_rejects_js(t, TypeError, writer1.write(1), 'write() promise should reject');
const readyPromise = promise_rejects_js(t, TypeError, writer1.ready, 'ready promise should reject');
const closedPromise1 = promise_rejects_js(t, TypeError, writer1.closed, 'closed promise should reject');
return Promise.all([writePromise1, readyPromise, closedPromise1, writePromise2, closePromise, closedPromise2])
.then(() => {
assert_array_equals(ws.events, ['write', 0, 'close'], 'sink.write() should only be called once');
});
}, 'original reader should error when new reader is created within strategy.size()');

View File

@ -0,0 +1,163 @@
// META: global=window,worker,shadowrealm
// META: script=../resources/test-utils.js
// META: script=../resources/recording-streams.js
'use strict';
const error1 = { name: 'error1' };
promise_test(() => {
let resolveStartPromise;
const ws = recordingWritableStream({
start() {
return new Promise(resolve => {
resolveStartPromise = resolve;
});
}
});
const writer = ws.getWriter();
assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
writer.write('a');
assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after writer.write()');
// Wait and verify that write isn't called.
return flushAsyncEvents()
.then(() => {
assert_array_equals(ws.events, [], 'write should not be called until start promise resolves');
resolveStartPromise();
return writer.ready;
})
.then(() => assert_array_equals(ws.events, ['write', 'a'],
'write should not be called until start promise resolves'));
}, 'underlying sink\'s write should not be called until start finishes');
promise_test(() => {
let resolveStartPromise;
const ws = recordingWritableStream({
start() {
return new Promise(resolve => {
resolveStartPromise = resolve;
});
}
});
const writer = ws.getWriter();
writer.close();
assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
// Wait and verify that write isn't called.
return flushAsyncEvents().then(() => {
assert_array_equals(ws.events, [], 'close should not be called until start promise resolves');
resolveStartPromise();
return writer.closed;
});
}, 'underlying sink\'s close should not be called until start finishes');
test(() => {
const passedError = new Error('horrible things');
let writeCalled = false;
let closeCalled = false;
assert_throws_exactly(passedError, () => {
// recordingWritableStream cannot be used here because the exception in the
// constructor prevents assigning the object to a variable.
new WritableStream({
start() {
throw passedError;
},
write() {
writeCalled = true;
},
close() {
closeCalled = true;
}
});
}, 'constructor should throw passedError');
assert_false(writeCalled, 'write should not be called');
assert_false(closeCalled, 'close should not be called');
}, 'underlying sink\'s write or close should not be called if start throws');
promise_test(() => {
const ws = recordingWritableStream({
start() {
return Promise.reject();
}
});
// Wait and verify that write or close aren't called.
return flushAsyncEvents()
.then(() => assert_array_equals(ws.events, [], 'write and close should not be called'));
}, 'underlying sink\'s write or close should not be invoked if the promise returned by start is rejected');
promise_test(t => {
const ws = new WritableStream({
start() {
return {
then(onFulfilled, onRejected) { onRejected(error1); }
};
}
});
return promise_rejects_exactly(t, error1, ws.getWriter().closed, 'closed promise should be rejected');
}, 'returning a thenable from start() should work');
promise_test(t => {
const ws = recordingWritableStream({
start(controller) {
controller.error(error1);
}
});
return promise_rejects_exactly(t, error1, ws.getWriter().write('a'), 'write() should reject with the error')
.then(() => {
assert_array_equals(ws.events, [], 'sink write() should not have been called');
});
}, 'controller.error() during start should cause writes to fail');
promise_test(t => {
let controller;
let resolveStart;
const ws = recordingWritableStream({
start(c) {
controller = c;
return new Promise(resolve => {
resolveStart = resolve;
});
}
});
const writer = ws.getWriter();
const writePromise = writer.write('a');
const closePromise = writer.close();
controller.error(error1);
resolveStart();
return Promise.all([
promise_rejects_exactly(t, error1, writePromise, 'write() should fail'),
promise_rejects_exactly(t, error1, closePromise, 'close() should fail')
]).then(() => {
assert_array_equals(ws.events, [], 'sink write() and close() should not have been called');
});
}, 'controller.error() during async start should cause existing writes to fail');
promise_test(t => {
const events = [];
const promises = [];
function catchAndRecord(promise, name) {
promises.push(promise.then(t.unreached_func(`promise ${name} should not resolve`),
() => {
events.push(name);
}));
}
const ws = new WritableStream({
start() {
return Promise.reject();
}
}, { highWaterMark: 0 });
const writer = ws.getWriter();
catchAndRecord(writer.ready, 'ready');
catchAndRecord(writer.closed, 'closed');
catchAndRecord(writer.write(), 'write');
return Promise.all(promises)
.then(() => {
assert_array_equals(events, ['ready', 'write', 'closed'], 'promises should reject in standard order');
});
}, 'when start() rejects, writer promises should reject in standard order');

View File

@ -0,0 +1,284 @@
// META: global=window,worker,shadowrealm
// META: script=../resources/test-utils.js
// META: script=../resources/recording-streams.js
'use strict';
const error1 = new Error('error1');
error1.name = 'error1';
const error2 = new Error('error2');
error2.name = 'error2';
function writeArrayToStream(array, writableStreamWriter) {
array.forEach(chunk => writableStreamWriter.write(chunk));
return writableStreamWriter.close();
}
promise_test(() => {
let storage;
const ws = new WritableStream({
start() {
storage = [];
},
write(chunk) {
return delay(0).then(() => storage.push(chunk));
},
close() {
return delay(0);
}
});
const writer = ws.getWriter();
const input = [1, 2, 3, 4, 5];
return writeArrayToStream(input, writer)
.then(() => assert_array_equals(storage, input, 'correct data should be relayed to underlying sink'));
}, 'WritableStream should complete asynchronous writes before close resolves');
promise_test(() => {
const ws = recordingWritableStream();
const writer = ws.getWriter();
const input = [1, 2, 3, 4, 5];
return writeArrayToStream(input, writer)
.then(() => assert_array_equals(ws.events, ['write', 1, 'write', 2, 'write', 3, 'write', 4, 'write', 5, 'close'],
'correct data should be relayed to underlying sink'));
}, 'WritableStream should complete synchronous writes before close resolves');
promise_test(() => {
const ws = new WritableStream({
write() {
return 'Hello';
}
});
const writer = ws.getWriter();
const writePromise = writer.write('a');
return writePromise
.then(value => assert_equals(value, undefined, 'fulfillment value must be undefined'));
}, 'fulfillment value of ws.write() call should be undefined even if the underlying sink returns a non-undefined ' +
'value');
promise_test(() => {
let resolveSinkWritePromise;
const ws = new WritableStream({
write() {
return new Promise(resolve => {
resolveSinkWritePromise = resolve;
});
}
});
const writer = ws.getWriter();
assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
return writer.ready.then(() => {
const writePromise = writer.write('a');
let writePromiseResolved = false;
assert_not_equals(resolveSinkWritePromise, undefined, 'resolveSinkWritePromise should not be undefined');
assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after writer.write()');
return Promise.all([
writePromise.then(value => {
writePromiseResolved = true;
assert_equals(resolveSinkWritePromise, undefined, 'sinkWritePromise should be fulfilled before writePromise');
assert_equals(value, undefined, 'writePromise should be fulfilled with undefined');
}),
writer.ready.then(value => {
assert_equals(resolveSinkWritePromise, undefined, 'sinkWritePromise should be fulfilled before writer.ready');
assert_true(writePromiseResolved, 'writePromise should be fulfilled before writer.ready');
assert_equals(writer.desiredSize, 1, 'desiredSize should be 1 again');
assert_equals(value, undefined, 'writePromise should be fulfilled with undefined');
}),
flushAsyncEvents().then(() => {
resolveSinkWritePromise();
resolveSinkWritePromise = undefined;
})
]);
});
}, 'WritableStream should transition to waiting until write is acknowledged');
promise_test(t => {
let sinkWritePromiseRejectors = [];
const ws = new WritableStream({
write() {
const sinkWritePromise = new Promise((r, reject) => sinkWritePromiseRejectors.push(reject));
return sinkWritePromise;
}
});
const writer = ws.getWriter();
assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
return writer.ready.then(() => {
const writePromise = writer.write('a');
assert_equals(sinkWritePromiseRejectors.length, 1, 'there should be 1 rejector');
assert_equals(writer.desiredSize, 0, 'desiredSize should be 0');
const writePromise2 = writer.write('b');
assert_equals(sinkWritePromiseRejectors.length, 1, 'there should be still 1 rejector');
assert_equals(writer.desiredSize, -1, 'desiredSize should be -1');
const closedPromise = writer.close();
assert_equals(writer.desiredSize, -1, 'desiredSize should still be -1');
return Promise.all([
promise_rejects_exactly(t, error1, closedPromise,
'closedPromise should reject with the error returned from the sink\'s write method')
.then(() => assert_equals(sinkWritePromiseRejectors.length, 0,
'sinkWritePromise should reject before closedPromise')),
promise_rejects_exactly(t, error1, writePromise,
'writePromise should reject with the error returned from the sink\'s write method')
.then(() => assert_equals(sinkWritePromiseRejectors.length, 0,
'sinkWritePromise should reject before writePromise')),
promise_rejects_exactly(t, error1, writePromise2,
'writePromise2 should reject with the error returned from the sink\'s write method')
.then(() => assert_equals(sinkWritePromiseRejectors.length, 0,
'sinkWritePromise should reject before writePromise2')),
flushAsyncEvents().then(() => {
sinkWritePromiseRejectors[0](error1);
sinkWritePromiseRejectors = [];
})
]);
});
}, 'when write returns a rejected promise, queued writes and close should be cleared');
promise_test(t => {
const ws = new WritableStream({
write() {
throw error1;
}
});
const writer = ws.getWriter();
return promise_rejects_exactly(t, error1, writer.write('a'),
'write() should reject with the error returned from the sink\'s write method')
.then(() => promise_rejects_js(t, TypeError, writer.close(), 'close() should be rejected'));
}, 'when sink\'s write throws an error, the stream should become errored and the promise should reject');
promise_test(t => {
const ws = new WritableStream({
write(chunk, controller) {
controller.error(error1);
throw error2;
}
});
const writer = ws.getWriter();
return promise_rejects_exactly(t, error2, writer.write('a'),
'write() should reject with the error returned from the sink\'s write method ')
.then(() => {
return Promise.all([
promise_rejects_exactly(t, error1, writer.ready,
'writer.ready must reject with the error passed to the controller'),
promise_rejects_exactly(t, error1, writer.closed,
'writer.closed must reject with the error passed to the controller')
]);
});
}, 'writer.write(), ready and closed reject with the error passed to controller.error() made before sink.write' +
' rejection');
promise_test(() => {
const numberOfWrites = 1000;
let resolveFirstWritePromise;
let writeCount = 0;
const ws = new WritableStream({
write() {
++writeCount;
if (!resolveFirstWritePromise) {
return new Promise(resolve => {
resolveFirstWritePromise = resolve;
});
}
return Promise.resolve();
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
for (let i = 1; i < numberOfWrites; ++i) {
writer.write('a');
}
const writePromise = writer.write('a');
assert_equals(writeCount, 1, 'should have called sink\'s write once');
resolveFirstWritePromise();
return writePromise
.then(() =>
assert_equals(writeCount, numberOfWrites, `should have called sink's write ${numberOfWrites} times`));
});
}, 'a large queue of writes should be processed completely');
promise_test(() => {
const stream = recordingWritableStream();
const w = stream.getWriter();
const WritableStreamDefaultWriter = w.constructor;
w.releaseLock();
const writer = new WritableStreamDefaultWriter(stream);
return writer.ready.then(() => {
writer.write('a');
assert_array_equals(stream.events, ['write', 'a'], 'write() should be passed to sink');
});
}, 'WritableStreamDefaultWriter should work when manually constructed');
promise_test(() => {
let thenCalled = false;
const ws = new WritableStream({
write() {
return {
then(onFulfilled) {
thenCalled = true;
onFulfilled();
}
};
}
});
return ws.getWriter().write('a').then(() => assert_true(thenCalled, 'thenCalled should be true'));
}, 'returning a thenable from write() should work');
promise_test(() => {
const stream = new WritableStream();
const writer = stream.getWriter();
const WritableStreamDefaultWriter = writer.constructor;
assert_throws_js(TypeError, () => new WritableStreamDefaultWriter(stream),
'should not be able to construct on locked stream');
// If stream.[[writer]] no longer points to |writer| then the closed Promise
// won't work properly.
return Promise.all([writer.close(), writer.closed]);
}, 'failing DefaultWriter constructor should not release an existing writer');
promise_test(t => {
const ws = new WritableStream({
start() {
return Promise.reject(error1);
}
}, { highWaterMark: 0 });
const writer = ws.getWriter();
return Promise.all([
promise_rejects_exactly(t, error1, writer.ready, 'ready should be rejected'),
promise_rejects_exactly(t, error1, writer.write(), 'write() should be rejected')
]);
}, 'write() on a stream with HWM 0 should not cause the ready Promise to resolve');
promise_test(t => {
const ws = new WritableStream();
const writer = ws.getWriter();
writer.releaseLock();
return promise_rejects_js(t, TypeError, writer.write(), 'write should reject');
}, 'writing to a released writer should reject the returned promise');