Update web-platform-tests to revision 58eb04cecbbec2e18531ab440225e38944a9c444

This commit is contained in:
Josh Matthews 2017-04-17 12:06:02 +10:00 committed by Anthony Ramine
parent 25e8bf69e6
commit 665817d2a6
35333 changed files with 1818077 additions and 16036 deletions

View file

@ -424,4 +424,38 @@ promise_test(() => {
}, 'Closing must be propagated forward: shutdown must not occur until the final write completes');
promise_test(() => {
const rs = recordingReadableStream();
let resolveWritePromise;
const ws = recordingWritableStream({
write() {
return new Promise(resolve => {
resolveWritePromise = resolve;
});
}
});
let pipeComplete = false;
const pipePromise = rs.pipeTo(ws, { preventClose: true }).then(() => {
pipeComplete = true;
});
rs.controller.enqueue('a');
rs.controller.close();
// Flush async events and verify that no shutdown occurs.
return flushAsyncEvents().then(() => {
assert_array_equals(ws.events, ['write', 'a'],
'the chunk must have been written, but close must not have happened yet');
assert_equals(pipeComplete, false, 'the pipe must not be complete');
resolveWritePromise();
return pipePromise;
});
}, 'Closing must be propagated forward: shutdown must not occur until the final write completes; preventClose = true');
done();

View file

@ -124,6 +124,52 @@ promise_test(() => {
}, 'Piping from an empty ReadableStream into a WritableStream that does not desire chunks, but then the readable ' +
'stream becomes non-empty and the writable stream starts desiring chunks');
promise_test(() => {
const unreadChunks = ['b', 'c', 'd'];
const rs = recordingReadableStream({
pull(controller) {
controller.enqueue(unreadChunks.shift());
if (unreadChunks.length === 0) {
controller.close();
}
}
}, new CountQueuingStrategy({ highWaterMark: 0 }));
let resolveWritePromise;
const ws = recordingWritableStream({
write() {
if (!resolveWritePromise) {
// first write
return new Promise(resolve => {
resolveWritePromise = resolve;
});
}
return undefined;
}
}, new CountQueuingStrategy({ highWaterMark: 3 }));
const writer = ws.getWriter();
const firstWritePromise = writer.write('a');
assert_equals(writer.desiredSize, 2, 'after writing the writer\'s desiredSize must be 2');
writer.releaseLock();
// firstWritePromise won't settle until we call resolveWritePromise.
const pipePromise = rs.pipeTo(ws);
return flushAsyncEvents().then(() => {
assert_array_equals(ws.events, ['write', 'a']);
assert_equals(unreadChunks.length, 1, 'chunks should continue to be enqueued until the HWM is reached');
}).then(() => resolveWritePromise())
.then(() => Promise.all([firstWritePromise, pipePromise]))
.then(() => {
assert_array_equals(rs.events, ['pull', 'pull', 'pull']);
assert_array_equals(ws.events, ['write', 'a', 'write', 'b','write', 'c','write', 'd', 'close']);
});
}, 'Piping from a ReadableStream to a WritableStream that desires more chunks before finishing with previous ones');
promise_test(() => {
const desiredSizes = [];

View file

@ -155,7 +155,7 @@ promise_test(() => {
}, 'Piping from a ReadableStream for which a chunk becomes asynchronously readable after the pipeTo');
for (const preventAbort of [true, false]) {
promise_test(t => {
promise_test(() => {
const rs = new ReadableStream({
pull() {
@ -171,7 +171,7 @@ for (const preventAbort of [true, false]) {
}
for (const preventCancel of [true, false]) {
promise_test(t => {
promise_test(() => {
const rs = new ReadableStream({
pull(controller) {

View file

@ -71,19 +71,48 @@ promise_test(t => {
});
const ws = recordingWritableStream();
const writer = ws.getWriter();
writer.close();
const closePromise = writer.close();
writer.releaseLock();
return promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the readable stream\'s error').then(() => {
assert_array_equals(rs.events, []);
assert_array_equals(ws.events, ['close']);
assert_array_equals(ws.events, ['abort', error1]);
return Promise.all([
promise_rejects(t, error1, rs.getReader().closed, 'the readable stream must be errored with error1'),
ws.getWriter().closed
promise_rejects(t, new TypeError(), ws.getWriter().closed,
'closed must reject with a TypeError indicating the writable stream was aborted'),
promise_rejects(t, new TypeError(), closePromise,
'close() must reject with a TypeError indicating the writable stream was aborted'),
]);
});
}, 'Piping from an errored readable stream to a closing writable stream');
promise_test(t => {
const rs = recordingReadableStream({
start(c) {
c.error(error1);
}
});
const ws = recordingWritableStream();
const writer = ws.getWriter();
const closePromise = writer.close();
writer.releaseLock();
return flushAsyncEvents().then(() => {
return promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the readable stream\'s error').then(() => {
assert_array_equals(rs.events, []);
assert_array_equals(ws.events, ['close']);
return Promise.all([
promise_rejects(t, error1, rs.getReader().closed, 'the readable stream must be errored with error1'),
ws.getWriter().closed,
closePromise
]);
});
});
}, 'Piping from an errored readable stream to a closed writable stream');
promise_test(t => {

View file

@ -37,7 +37,7 @@ promise_test(() => {
assert_array_equals(chunks, [1, 2, 3, 4, 5]), 'chunks should match');
}, 'Piping through a duck-typed pass-through transform stream should work');
promise_test(t => {
promise_test(() => {
const transform = {
writable: new WritableStream({
start(c) {
@ -77,7 +77,7 @@ test(() => {
test(() => {
const dummy = {
pipeTo(args) {
pipeTo() {
return { not: 'a promise' };
}
};
@ -90,7 +90,7 @@ test(() => {
test(() => {
const dummy = {
pipeTo(args) {
pipeTo() {
return {
then() {},
this: 'is not a real promise'
@ -104,4 +104,44 @@ test(() => {
}, 'pipeThrough can handle calling a pipeTo that returns a non-promise thenable object');
promise_test(() => {
const dummy = {
pipeTo() {
return Promise.reject(new Error('this rejection should not be reported as unhandled'));
}
};
ReadableStream.prototype.pipeThrough.call(dummy, { });
// The test harness should complain about unhandled rejections by then.
return flushAsyncEvents();
}, 'pipeThrough should mark a real promise from a fake readable as handled');
test(() => {
let thenCalled = false
let catchCalled = false;
const dummy = {
pipeTo() {
const fakePromise = Object.create(Promise.prototype);
fakePromise.then = () => {
thenCalled = true;
};
fakePromise.catch = () => {
catchCalled = true;
};
assert_true(fakePromise instanceof Promise, 'fakePromise fools instanceof');
return fakePromise;
}
};
// An incorrect implementation which uses an internal method to mark the promise as handled will throw or crash here.
ReadableStream.prototype.pipeThrough.call(dummy, { });
// An incorrect implementation that tries to mark the promise as handled by calling .then() or .catch() on the object
// will fail these tests.
assert_false(thenCalled, 'then should not be called');
assert_false(catchCalled, 'catch should not be called');
}, 'pipeThrough should not be fooled by an object whose instanceof Promise returns true');
done();

View file

@ -15,9 +15,30 @@ test(() => {
test(() => {
// Constructing ReadableStream with an empty underlying byte source object as parameter shouldn't throw.
new ReadableStream({ type: 'bytes' });
new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' });
// Constructor must perform ToString(type).
new ReadableStream({ type: { toString() {return 'bytes';} } })
.getReader({ mode: 'byob' });
new ReadableStream({ type: { toString: null, valueOf() {return 'bytes';} } })
.getReader({ mode: 'byob' });
}, 'ReadableStream with byte source can be constructed with no errors');
test(() => {
const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
const rs = new ReadableStream({ type: 'bytes' });
let reader = rs.getReader({ mode: { toString() { return 'byob'; } } });
assert_true(reader instanceof ReadableStreamBYOBReader, 'must give a BYOB reader');
reader.releaseLock();
reader = rs.getReader({ mode: { toString: null, valueOf() {return 'byob';} } });
assert_true(reader instanceof ReadableStreamBYOBReader, 'must give a BYOB reader');
reader.releaseLock();
reader = rs.getReader({ mode: 'byob', notmode: 'ignored' });
assert_true(reader instanceof ReadableStreamBYOBReader, 'must give a BYOB reader');
}, 'getReader({mode}) must perform ToString()');
promise_test(() => {
let startCalled = false;
let startCalledBeforePull = false;
@ -101,7 +122,7 @@ promise_test(t => {
}, 'ReadableStream with byte source: Construct with highWaterMark of 0');
test(() => {
const rs = new ReadableStream({
new ReadableStream({
start(c) {
assert_equals(c.desiredSize, 10, 'desiredSize must start at the highWaterMark');
c.close();
@ -114,7 +135,7 @@ test(() => {
}, 'ReadableStream with byte source: desiredSize when closed');
test(() => {
const rs = new ReadableStream({
new ReadableStream({
start(c) {
assert_equals(c.desiredSize, 10, 'desiredSize must start at the highWaterMark');
c.error();

View file

@ -153,7 +153,7 @@ promise_test(() => {
});
rs.cancel();
assert_throws(new TypeError, () => controller.enqueue('a'), 'Calling enqueue after canceling should throw');
assert_throws(new TypeError(), () => controller.enqueue('a'), 'Calling enqueue after canceling should throw');
return rs.getReader().closed;
@ -171,7 +171,7 @@ promise_test(() => {
});
rs.cancel();
assert_throws(new TypeError, () => controller.enqueue('c'), 'Calling enqueue after canceling should throw');
assert_throws(new TypeError(), () => controller.enqueue('c'), 'Calling enqueue after canceling should throw');
return rs.getReader().closed;

View file

@ -5,15 +5,15 @@ if (self.importScripts) {
self.importScripts('/resources/testharness.js');
}
let ReadableStreamReader;
let ReadableStreamDefaultReader;
let ReadableStreamController;
test(() => {
// It's not exposed globally, but we test a few of its properties here.
ReadableStreamReader = (new ReadableStream()).getReader().constructor;
ReadableStreamDefaultReader = (new ReadableStream()).getReader().constructor;
}, 'Can get the ReadableStreamReader constructor indirectly');
}, 'Can get the ReadableStreamDefaultReader constructor indirectly');
test(() => {
@ -29,7 +29,7 @@ test(() => {
function fakeReadableStream() {
return {
cancel() { return Promise.resolve(); },
getReader() { return new ReadableStreamReader(new ReadableStream()); },
getReader() { return new ReadableStreamDefaultReader(new ReadableStream()); },
pipeThrough(obj) { return obj.readable; },
pipeTo() { return Promise.resolve(); },
tee() { return [realReadableStream(), realReadableStream()]; }
@ -40,7 +40,7 @@ function realReadableStream() {
return new ReadableStream();
}
function fakeReadableStreamReader() {
function fakeReadableStreamDefaultReader() {
return {
get closed() { return Promise.resolve(); },
cancel() { return Promise.resolve(); },
@ -77,44 +77,44 @@ test(() => {
test(() => {
assert_throws(new TypeError(), () => new ReadableStreamReader(fakeReadableStream()),
'Constructing a ReadableStreamReader should throw');
assert_throws(new TypeError(), () => new ReadableStreamDefaultReader(fakeReadableStream()),
'Constructing a ReadableStreamDefaultReader should throw');
}, 'ReadableStreamReader enforces a brand check on its argument');
}, 'ReadableStreamDefaultReader enforces a brand check on its argument');
promise_test(t => {
return Promise.all([
getterRejects(t, ReadableStreamReader.prototype, 'closed', fakeReadableStreamReader()),
getterRejects(t, ReadableStreamReader.prototype, 'closed', realReadableStream())
getterRejects(t, ReadableStreamDefaultReader.prototype, 'closed', fakeReadableStreamDefaultReader()),
getterRejects(t, ReadableStreamDefaultReader.prototype, 'closed', realReadableStream())
]);
}, 'ReadableStreamReader.prototype.closed enforces a brand check');
}, 'ReadableStreamDefaultReader.prototype.closed enforces a brand check');
promise_test(t => {
return Promise.all([
methodRejects(t, ReadableStreamReader.prototype, 'cancel', fakeReadableStreamReader()),
methodRejects(t, ReadableStreamReader.prototype, 'cancel', realReadableStream())
methodRejects(t, ReadableStreamDefaultReader.prototype, 'cancel', fakeReadableStreamDefaultReader()),
methodRejects(t, ReadableStreamDefaultReader.prototype, 'cancel', realReadableStream())
]);
}, 'ReadableStreamReader.prototype.cancel enforces a brand check');
}, 'ReadableStreamDefaultReader.prototype.cancel enforces a brand check');
promise_test(t => {
return Promise.all([
methodRejects(t, ReadableStreamReader.prototype, 'read', fakeReadableStreamReader()),
methodRejects(t, ReadableStreamReader.prototype, 'read', realReadableStream())
methodRejects(t, ReadableStreamDefaultReader.prototype, 'read', fakeReadableStreamDefaultReader()),
methodRejects(t, ReadableStreamDefaultReader.prototype, 'read', realReadableStream())
]);
}, 'ReadableStreamReader.prototype.read enforces a brand check');
}, 'ReadableStreamDefaultReader.prototype.read enforces a brand check');
test(() => {
methodThrows(ReadableStreamReader.prototype, 'releaseLock', fakeReadableStreamReader());
methodThrows(ReadableStreamReader.prototype, 'releaseLock', realReadableStream());
methodThrows(ReadableStreamDefaultReader.prototype, 'releaseLock', fakeReadableStreamDefaultReader());
methodThrows(ReadableStreamDefaultReader.prototype, 'releaseLock', realReadableStream());
}, 'ReadableStreamReader.prototype.releaseLock enforces a brand check');
}, 'ReadableStreamDefaultReader.prototype.releaseLock enforces a brand check');
test(() => {

View file

@ -39,10 +39,10 @@ promise_test(() => {
// We call delay multiple times to avoid cancelling too early for the
// source to enqueue at least one chunk.
const cancel = delay(5).then(() => delay(5)).then(() => delay(5)).then(() => {
let cancelPromise = reader.cancel();
const cancelPromise = reader.cancel();
assert_false(cancellationFinished, 'cancellation in source should happen later');
return cancelPromise;
})
});
return readableStreamToArray(rs, reader).then(chunks => {
assert_greater_than(chunks.length, 0, 'at least one chunk should be read');

View file

@ -1,11 +1,11 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>readable-stream-reader.js dedicated worker wrapper file</title>
<title>default-reader.js dedicated worker wrapper file</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
'use strict';
fetch_tests_from_worker(new Worker('readable-stream-reader.js'));
fetch_tests_from_worker(new Worker('default-reader.js'));
</script>

View file

@ -1,10 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>readable-stream-reader.js browser context wrapper file</title>
<title>default-reader.js browser context wrapper file</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/rs-utils.js"></script>
<script src="readable-stream-reader.js"></script>
<script src="default-reader.js"></script>

View file

@ -5,29 +5,29 @@ if (self.importScripts) {
self.importScripts('/resources/testharness.js');
}
let ReadableStreamReader;
let ReadableStreamDefaultReader;
test(() => {
// It's not exposed globally, but we test a few of its properties here.
ReadableStreamReader = (new ReadableStream()).getReader().constructor;
ReadableStreamDefaultReader = (new ReadableStream()).getReader().constructor;
}, 'Can get the ReadableStreamReader constructor indirectly');
}, 'Can get the ReadableStreamDefaultReader constructor indirectly');
test(() => {
assert_throws(new TypeError(), () => new ReadableStreamReader('potato'));
assert_throws(new TypeError(), () => new ReadableStreamReader({}));
assert_throws(new TypeError(), () => new ReadableStreamReader());
assert_throws(new TypeError(), () => new ReadableStreamDefaultReader('potato'));
assert_throws(new TypeError(), () => new ReadableStreamDefaultReader({}));
assert_throws(new TypeError(), () => new ReadableStreamDefaultReader());
}, 'ReadableStreamReader constructor should get a ReadableStream object as argument');
}, 'ReadableStreamDefaultReader constructor should get a ReadableStream object as argument');
test(() => {
const methods = ['cancel', 'constructor', 'read', 'releaseLock'];
const properties = methods.concat(['closed']).sort();
const rsReader = new ReadableStreamReader(new ReadableStream());
const rsReader = new ReadableStreamDefaultReader(new ReadableStream());
const proto = Object.getPrototypeOf(rsReader);
assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
@ -56,41 +56,41 @@ test(() => {
assert_equals(typeof rsReader.releaseLock, 'function', 'has a releaseLock method');
assert_equals(rsReader.releaseLock.length, 0, 'releaseLock has no parameters');
}, 'ReadableStreamReader instances should have the correct list of properties');
}, 'ReadableStreamDefaultReader instances should have the correct list of properties');
test(() => {
const rsReader = new ReadableStreamReader(new ReadableStream());
const rsReader = new ReadableStreamDefaultReader(new ReadableStream());
assert_equals(rsReader.closed, rsReader.closed, 'closed should return the same promise');
}, 'ReadableStreamReader closed should always return the same promise object');
}, 'ReadableStreamDefaultReader closed should always return the same promise object');
test(() => {
const rs = new ReadableStream();
new ReadableStreamReader(rs); // Constructing directly the first time should be fine.
assert_throws(new TypeError(), () => new ReadableStreamReader(rs),
new ReadableStreamDefaultReader(rs); // Constructing directly the first time should be fine.
assert_throws(new TypeError(), () => new ReadableStreamDefaultReader(rs),
'constructing directly the second time should fail');
}, 'Constructing a ReadableStreamReader directly should fail if the stream is already locked (via direct ' +
}, 'Constructing a ReadableStreamDefaultReader directly should fail if the stream is already locked (via direct ' +
'construction)');
test(() => {
const rs = new ReadableStream();
new ReadableStreamReader(rs); // Constructing directly should be fine.
new ReadableStreamDefaultReader(rs); // Constructing directly should be fine.
assert_throws(new TypeError(), () => rs.getReader(), 'getReader() should fail');
}, 'Getting a ReadableStreamReader via getReader should fail if the stream is already locked (via direct ' +
}, 'Getting a ReadableStreamDefaultReader via getReader should fail if the stream is already locked (via direct ' +
'construction)');
test(() => {
const rs = new ReadableStream();
rs.getReader(); // getReader() should be fine.
assert_throws(new TypeError(), () => new ReadableStreamReader(rs), 'constructing directly should fail');
assert_throws(new TypeError(), () => new ReadableStreamDefaultReader(rs), 'constructing directly should fail');
}, 'Constructing a ReadableStreamReader directly should fail if the stream is already locked (via getReader)');
}, 'Constructing a ReadableStreamDefaultReader directly should fail if the stream is already locked (via getReader)');
test(() => {
@ -98,7 +98,7 @@ test(() => {
rs.getReader(); // getReader() should be fine.
assert_throws(new TypeError(), () => rs.getReader(), 'getReader() should fail');
}, 'Getting a ReadableStreamReader via getReader should fail if the stream is already locked (via getReader)');
}, 'Getting a ReadableStreamDefaultReader via getReader should fail if the stream is already locked (via getReader)');
test(() => {
@ -108,9 +108,9 @@ test(() => {
}
});
new ReadableStreamReader(rs); // Constructing directly should not throw.
new ReadableStreamDefaultReader(rs); // Constructing directly should not throw.
}, 'Constructing a ReadableStreamReader directly should be OK if the stream is closed');
}, 'Constructing a ReadableStreamDefaultReader directly should be OK if the stream is closed');
test(() => {
@ -121,9 +121,9 @@ test(() => {
}
});
new ReadableStreamReader(rs); // Constructing directly should not throw.
new ReadableStreamDefaultReader(rs); // Constructing directly should not throw.
}, 'Constructing a ReadableStreamReader directly should be OK if the stream is errored');
}, 'Constructing a ReadableStreamDefaultReader directly should be OK if the stream is errored');
promise_test(() => {
@ -332,7 +332,7 @@ promise_test(t => {
controller.error();
return promise;
}, 'ReadableStreamReader closed promise should be rejected with undefined if that is the error');
}, 'ReadableStreamDefaultReader closed promise should be rejected with undefined if that is the error');
promise_test(t => {
@ -350,7 +350,8 @@ promise_test(t => {
}
);
}, 'ReadableStreamReader: if start rejects with no parameter, it should error the stream with an undefined error');
}, 'ReadableStreamDefaultReader: if start rejects with no parameter, it should error the stream with an undefined ' +
'error');
promise_test(t => {
@ -367,7 +368,7 @@ promise_test(t => {
controller.error(theError);
return promise;
}, 'Erroring a ReadableStream after checking closed should reject ReadableStreamReader closed promise');
}, 'Erroring a ReadableStream after checking closed should reject ReadableStreamDefaultReader closed promise');
promise_test(t => {
@ -386,7 +387,7 @@ promise_test(t => {
return promise_rejects(t, theError, rs.getReader().closed);
}, 'Erroring a ReadableStream before checking closed should reject ReadableStreamReader closed promise');
}, 'Erroring a ReadableStream before checking closed should reject ReadableStreamDefaultReader closed promise');
promise_test(() => {

View file

@ -1,6 +1,6 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>readable-stream-reader.js service worker wrapper file</title>
<title>default-reader.js service worker wrapper file</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
@ -8,5 +8,5 @@
<script>
'use strict';
service_worker_test('readable-stream-reader.js', 'Service worker test setup');
service_worker_test('default-reader.js', 'Service worker test setup');
</script>

View file

@ -1,11 +1,11 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>readable-stream-reader.js shared worker wrapper file</title>
<title>default-reader.js shared worker wrapper file</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
'use strict';
fetch_tests_from_worker(new SharedWorker('readable-stream-reader.js'));
fetch_tests_from_worker(new SharedWorker('default-reader.js'));
</script>

View file

@ -70,6 +70,6 @@ promise_test(() => {
return delay(50).then(() => assert_throws(new TypeError(), () => rs.getReader(),
'old reader should still be locking the stream even after garbage collection'));
}, 'Garbage-collecting a ReadableStreamReader should not unlock its stream');
}, 'Garbage-collecting a ReadableStreamDefaultReader should not unlock its stream');
done();

View file

@ -6,6 +6,9 @@ if (self.importScripts) {
self.importScripts('/resources/testharness.js');
}
const error1 = new Error('error1');
error1.name = 'error1';
test(() => {
new ReadableStream(); // ReadableStream constructed with no parameters
@ -32,6 +35,8 @@ test(() => {
'constructor should throw when the type is empty string');
assert_throws(new RangeError(), () => new ReadableStream({ type: 'asdf' }),
'constructor should throw when the type is asdf');
assert_throws(error1, () => new ReadableStream({ type: { get toString() {throw error1;} } }), 'constructor should throw when ToString() throws');
assert_throws(error1, () => new ReadableStream({ type: { toString() {throw error1;} } }), 'constructor should throw when ToString() throws');
}, 'ReadableStream can\'t be constructed with an invalid type');
@ -157,6 +162,13 @@ test(() => {
}, 'ReadableStream start controller parameter should be extensible');
test(() => {
(new ReadableStream()).getReader(undefined);
(new ReadableStream()).getReader({});
(new ReadableStream()).getReader({ mode: undefined, notmode: 'ignored' });
assert_throws(new RangeError(), () => (new ReadableStream()).getReader({ mode: 'potato' }));
}, 'default ReadableStream getReader() should only accept mode:undefined');
promise_test(() => {
function SimpleStreamSource() {}
@ -195,7 +207,9 @@ promise_test(() => {
const theError = new Error('rejected!');
const rs = new ReadableStream({
start() {
return delay(1).then(() => { throw theError; });
return delay(1).then(() => {
throw theError;
});
}
});
@ -733,7 +747,7 @@ promise_test(() => {
}, 'ReadableStream: should call underlying source methods as methods');
test(() => {
const rs = new ReadableStream({
new ReadableStream({
start(c) {
assert_equals(c.desiredSize, 10, 'desiredSize must start at highWaterMark');
c.close();
@ -745,7 +759,7 @@ test(() => {
}, 'ReadableStream: desiredSize when closed');
test(() => {
const rs = new ReadableStream({
new ReadableStream({
start(c) {
assert_equals(c.desiredSize, 10, 'desiredSize must start at highWaterMark');
c.error();

View file

@ -9,8 +9,8 @@ test(() => {
let pipeToArguments;
const thisValue = {
pipeTo() {
pipeToArguments = arguments;
pipeTo(...args) {
pipeToArguments = args;
}
};

View file

@ -52,18 +52,16 @@ self.recordingWritableStream = (extras = {}, strategy) => {
return undefined;
},
write(chunk) {
write(chunk, controller) {
stream.events.push('write', chunk);
if (extras.write) {
return extras.write(chunk);
return extras.write(chunk, controller);
}
return undefined;
},
close(...args) {
assert_array_equals(args, [controllerToCopyOver], 'close must always be called with the controller');
close() {
stream.events.push('close');
if (extras.close) {

View file

@ -14,9 +14,7 @@ error2.name = 'error2';
promise_test(t => {
const ws = new WritableStream({
write() {
return new Promise(() => { }); // forever-pending, so normally .ready would not fulfill.
}
write: t.unreached_func('write() should not be called')
});
const writer = ws.getWriter();
@ -66,11 +64,12 @@ promise_test(t => {
.then(() => {
const writer = ws.getWriter();
writer.abort();
const abortPromise = writer.abort();
return Promise.all([
promise_rejects(t, new TypeError(), writer.write(1), 'write(1) must reject with a TypeError'),
promise_rejects(t, new TypeError(), writer.write(2), 'write(2) must reject with a TypeError')
promise_rejects(t, new TypeError(), writer.write(2), 'write(2) must reject with a TypeError'),
abortPromise
]);
})
.then(() => {
@ -169,20 +168,20 @@ promise_test(t => {
}, 'WritableStream if sink\'s abort throws, for an abort performed during a write, the promise returned by ' +
'ws.abort() rejects');
test(() => {
promise_test(() => {
const ws = recordingWritableStream();
const writer = ws.getWriter();
writer.abort(error1);
assert_array_equals(ws.events, ['abort', error1]);
return writer.abort(error1).then(() => {
assert_array_equals(ws.events, ['abort', error1]);
});
}, 'Aborting a WritableStream passes through the given reason');
promise_test(t => {
const ws = new WritableStream();
const writer = ws.getWriter();
writer.abort(error1);
const abortPromise = writer.abort(error1);
const events = [];
writer.ready.catch(() => {
@ -193,6 +192,7 @@ promise_test(t => {
});
return Promise.all([
abortPromise,
promise_rejects(t, new TypeError(), writer.write(), 'writing should reject with a TypeError'),
promise_rejects(t, new TypeError(), writer.close(), 'closing should reject with a TypeError'),
promise_rejects(t, new TypeError(), writer.abort(), 'aborting should reject with a TypeError'),
@ -216,19 +216,22 @@ promise_test(t => {
}, 'Aborting a WritableStream causes any outstanding write() promises to be rejected with a TypeError');
promise_test(t => {
const ws = new WritableStream();
const ws = recordingWritableStream();
const writer = ws.getWriter();
const closePromise = writer.close();
writer.abort(error1);
const abortPromise = writer.abort(error1);
return Promise.all([
promise_rejects(t, new TypeError(), writer.closed, 'closed should reject with a TypeError'),
promise_rejects(t, new TypeError(), closePromise, 'close() should reject with a TypeError')
]);
promise_rejects(t, new TypeError(), closePromise, 'close() should reject with a TypeError'),
abortPromise
]).then(() => {
assert_array_equals(ws.events, ['abort', error1]);
});
}, 'Closing but then immediately aborting a WritableStream causes the stream to error');
promise_test(t => {
promise_test(() => {
let resolveClose;
const ws = new WritableStream({
close() {
@ -245,12 +248,12 @@ promise_test(t => {
const abortPromise = writer.abort(error1);
resolveClose();
return Promise.all([
promise_rejects(t, new TypeError(), writer.closed, 'closed should reject with a TypeError'),
writer.closed,
abortPromise,
closePromise
]);
});
}, 'Closing a WritableStream and aborting it while it closes causes the stream to error');
}, 'Closing a WritableStream and aborting it while it closes causes the stream to ignore the abort attempt');
promise_test(() => {
const ws = new WritableStream();
@ -308,11 +311,11 @@ promise_test(t => {
return writer.ready.then(() => {
const writePromise = writer.write('a');
writer.abort(error1);
let closedResolved = false;
let closedRejected = false;
return Promise.all([
writePromise.then(() => assert_false(closedResolved, '.closed should not resolve before write()')),
writePromise.then(() => assert_false(closedRejected, '.closed should not resolve before write()')),
promise_rejects(t, new TypeError(), writer.closed, '.closed should reject').then(() => {
closedResolved = true;
closedRejected = true;
})
]);
});
@ -328,17 +331,18 @@ promise_test(t => {
return writer.ready.then(() => {
const writePromise = writer.write('a');
const abortPromise = writer.abort(error2);
let closedResolved = false;
let closedRejected = false;
return Promise.all([
promise_rejects(t, error1, writePromise, 'write() should reject')
.then(() => assert_false(closedResolved, '.closed should not resolve before write()')),
promise_rejects(t, error1, writer.closed, '.closed should reject')
.then(() => assert_false(closedRejected, '.closed should not resolve before write()')),
promise_rejects(t, new TypeError(), writer.closed, '.closed should reject')
.then(() => {
closedResolved = true;
closedRejected = true;
}),
promise_rejects(t, error1, abortPromise, 'abort() should reject')]);
abortPromise
]);
});
}, '.closed should not resolve before rejected write(); write() error should overwrite abort() error');
}, '.closed should not resolve before rejected write(); write() error should not overwrite abort() error');
promise_test(t => {
const ws = new WritableStream({
@ -370,13 +374,13 @@ promise_test(t => {
return writer.ready.then(() => {
const settlementOrder = [];
return Promise.all([
promise_rejects(t, error1, writer.write('1'), 'pending write should be rejected')
promise_rejects(t, error1, writer.write('1'), 'in-flight write should be rejected')
.then(() => settlementOrder.push(1)),
promise_rejects(t, error1, writer.write('2'), 'first queued write should be rejected')
promise_rejects(t, new TypeError(), writer.write('2'), 'first queued write should be rejected')
.then(() => settlementOrder.push(2)),
promise_rejects(t, error1, writer.write('3'), 'second queued write should be rejected')
promise_rejects(t, new TypeError(), writer.write('3'), 'second queued write should be rejected')
.then(() => settlementOrder.push(3)),
promise_rejects(t, error1, writer.abort(error1), 'abort should be rejected')
writer.abort(error2)
]).then(() => assert_array_equals([1, 2, 3], settlementOrder, 'writes should be satisfied in order'));
});
}, 'writes should be satisfied in order after rejected write when aborting');
@ -391,49 +395,44 @@ promise_test(t => {
return writer.ready.then(() => {
return Promise.all([
promise_rejects(t, error1, writer.write('a'), 'writer.write() should reject with error from underlying write()'),
promise_rejects(t, error1, writer.close(), 'writer.close() should reject with error from underlying write()'),
promise_rejects(t, error1, writer.abort(), 'writer.abort() should reject with error from underlying write()')
promise_rejects(t, new TypeError(), writer.close(),
'writer.close() should reject with error from underlying write()'),
writer.abort()
]);
});
}, 'close() should use error from underlying write() on abort');
}, 'close() should reject with TypeError when abort() is first error');
promise_test(() => {
let resolveWrite;
let abortCalled = false;
const ws = new WritableStream({
const ws = recordingWritableStream({
write() {
return new Promise(resolve => {
resolveWrite = resolve;
});
},
abort() {
abortCalled = true;
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
writer.write('a');
const abortPromise = writer.abort();
const abortPromise = writer.abort('b');
return flushAsyncEvents().then(() => {
assert_false(abortCalled, 'abort should not be called while write is pending');
assert_array_equals(ws.events, ['write', 'a'], 'abort should not be called while write is in-flight');
resolveWrite();
return abortPromise.then(() => assert_true(abortCalled, 'abort should be called'));
return abortPromise.then(() => {
assert_array_equals(ws.events, ['write', 'a', 'abort', 'b'], 'abort should be called after the write finishes');
});
});
});
}, 'underlying abort() should not be called until underlying write() completes');
promise_test(() => {
let resolveClose;
let abortCalled = false;
const ws = new WritableStream({
const ws = recordingWritableStream({
close() {
return new Promise(resolve => {
resolveClose = resolve;
});
},
abort() {
abortCalled = true;
}
});
@ -442,10 +441,10 @@ promise_test(() => {
writer.close();
const abortPromise = writer.abort();
return flushAsyncEvents().then(() => {
assert_false(abortCalled, 'underlying abort should not be called while close is pending');
assert_array_equals(ws.events, ['close'], 'abort should not be called while close is in-flight');
resolveClose();
return abortPromise.then(() => {
assert_false(abortCalled, 'underlying abort should not be called after close completes');
assert_array_equals(ws.events, ['close'], 'abort should not be called');
});
});
});
@ -470,7 +469,7 @@ promise_test(t => {
const closePromise = writer.close();
const abortPromise = writer.abort();
return flushAsyncEvents().then(() => {
assert_false(abortCalled, 'underlying abort should not be called while close is pending');
assert_false(abortCalled, 'underlying abort should not be called while close is in-flight');
rejectClose(error1);
return promise_rejects(t, error1, abortPromise, 'abort should reject with the same reason').then(() => {
return promise_rejects(t, error1, closePromise, 'close should reject with the same reason');
@ -484,15 +483,11 @@ promise_test(t => {
promise_test(t => {
let resolveWrite;
let abortCalled = false;
const ws = new WritableStream({
const ws = recordingWritableStream({
write() {
return new Promise(resolve => {
resolveWrite = resolve;
});
},
abort() {
abortCalled = true;
}
});
@ -500,17 +495,36 @@ promise_test(t => {
return writer.ready.then(() => {
writer.write('a');
const closePromise = writer.close();
const abortPromise = writer.abort();
const abortPromise = writer.abort('b');
return flushAsyncEvents().then(() => {
assert_false(abortCalled, 'abort should not be called while write is pending');
assert_array_equals(ws.events, ['write', 'a'], 'abort should not be called while write is in-flight');
resolveWrite();
return abortPromise.then(() => {
assert_true(abortCalled, 'abort should be called after write completes');
assert_array_equals(ws.events, ['write', 'a', 'abort', 'b'], 'abort should be called after write completes');
return promise_rejects(t, new TypeError(), closePromise, 'promise returned by close() should be rejected');
});
});
});
}, 'underlying abort() should be called while closing if underlying close() has not started yet');
}, 'an abort() that happens during a write() should trigger the underlying abort() even with a close() queued');
promise_test(t => {
const ws = new WritableStream({
write() {
return new Promise(() => {});
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
writer.write('a');
writer.abort();
writer.releaseLock();
const writer2 = ws.getWriter();
return promise_rejects(t, new TypeError(), writer2.ready,
'ready of the second writer should be rejected with a TypeError');
});
}, 'if a writer is created for a stream with a pending abort, its ready should be rejected with a TypeError');
promise_test(() => {
const ws = new WritableStream();
@ -518,11 +532,13 @@ promise_test(() => {
return writer.ready.then(() => {
const closePromise = writer.close();
const abortPromise = writer.abort();
let closeResolved = false;
Promise.all([
closePromise.then(() => { closeResolved = true; }),
abortPromise.then(() => { assert_true(closeResolved, 'close() promise should resolve before abort() promise'); })
]);
const events = [];
return Promise.all([
closePromise.then(() => { events.push('close'); }),
abortPromise.then(() => { events.push('abort'); })
]).then(() => {
assert_array_equals(events, ['close', 'abort']);
});
});
}, 'writer close() promise should resolve before abort() promise');
@ -569,7 +585,7 @@ promise_test(t => {
});
abortPromise = writer.abort(error1);
abortPromise.catch(() => {
abortPromise.then(() => {
events.push('abortPromise');
});
@ -588,21 +604,20 @@ promise_test(t => {
return Promise.all([
promise_rejects(t, error2, writePromise,
'writePromise must reject with the error returned from the sink\'s write method'),
promise_rejects(t, error2, abortPromise,
'abortPromise must reject with the error returned from the sink\'s write method'),
promise_rejects(t, error2, writer.closed,
'writer.closed must reject with the error returned from the sink\'s write method'),
abortPromise,
promise_rejects(t, new TypeError(), writer.closed,
'writer.closed must reject with an error indicating abort'),
flushAsyncEvents()
]);
}).then(() => {
assert_array_equals(events, ['writePromise', 'abortPromise', 'closed'],
'writePromise, abortPromise and writer.closed must reject');
'writePromise, abortPromise and writer.closed must settle');
const writePromise3 = writer.write('a');
return Promise.all([
promise_rejects(t, new TypeError(), writePromise3,
'writePromise3 must reject with an error indicating the stream has already been errored'),
'writePromise3 must reject with an error indicating abort'),
promise_rejects(t, new TypeError(), writer.ready,
'writer.ready must be still rejected with the error indicating abort')
]);
@ -616,7 +631,7 @@ promise_test(t => {
'writer.closed must be rejected with an error indicating release')
]);
});
}, 'writer.abort() while there is a pending write, and then finish the write with rejection');
}, 'writer.abort() while there is an in-flight write, and then finish the write with rejection');
promise_test(t => {
let resolveWrite;
@ -649,7 +664,7 @@ promise_test(t => {
});
abortPromise = writer.abort(error1);
abortPromise.catch(() => {
abortPromise.then(() => {
events.push('abortPromise');
});
@ -663,13 +678,14 @@ promise_test(t => {
}).then(() => {
assert_array_equals(events, [], 'writePromise, abortPromise and writer.closed must not be fulfilled/rejected yet');
// This error is too late to change anything. abort() has already changed the stream state to 'erroring'.
controller.error(error2);
const writePromise3 = writer.write('a');
return Promise.all([
promise_rejects(t, new TypeError(), writePromise3,
'writePromise3 must reject with an error indicating the stream has already been errored'),
'writePromise3 must reject with an error indicating abort'),
promise_rejects(t, new TypeError(), writer.ready,
'writer.ready must be still rejected with the error indicating abort'),
flushAsyncEvents()
@ -677,29 +693,28 @@ promise_test(t => {
}).then(() => {
assert_array_equals(
events, [],
'writePromise, abortPromise and writer.closed must not be fulfilled/rejected yet even after '
+ 'controller.error() call');
'writePromise, abortPromise and writer.closed must not be fulfilled/rejected yet even after ' +
'controller.error() call');
resolveWrite();
return Promise.all([
writePromise,
promise_rejects(t, error2, abortPromise,
'abortPromise must reject with the error passed to the controller\'s error method'),
promise_rejects(t, error2, writer.closed,
'writer.closed must reject with the error passed to the controller\'s error method'),
abortPromise,
promise_rejects(t, new TypeError(), writer.closed,
'writer.closed must reject with an error indicating abort'),
flushAsyncEvents()
]);
}).then(() => {
assert_array_equals(events, ['writePromise', 'abortPromise', 'closed'],
'writePromise, abortPromise and writer.closed must reject');
'writePromise, abortPromise and writer.closed must settle');
const writePromise4 = writer.write('a');
return Promise.all([
writePromise,
promise_rejects(t, new TypeError(), writePromise4,
'writePromise4 must reject with an error indicating that the stream has already been errored'),
'writePromise4 must reject with an error indicating abort'),
promise_rejects(t, new TypeError(), writer.ready,
'writer.ready must be still rejected with the error indicating abort')
]);
@ -713,12 +728,105 @@ promise_test(t => {
'writer.closed must be rejected with an error indicating release')
]);
});
}, 'writer.abort(), controller.error() while there is a pending write, and then finish the write');
}, 'writer.abort(), controller.error() while there is an in-flight write, and then finish the write');
promise_test(t => {
let resolveClose;
let controller;
const ws = new WritableStream({
start(c) {
controller = c;
},
close() {
return new Promise(resolve => {
resolveClose = resolve;
});
}
});
let closePromise;
let abortPromise;
const events = [];
const writer = ws.getWriter();
writer.closed.then(() => {
events.push('closed');
});
// Wait for ws to start
return flushAsyncEvents().then(() => {
closePromise = writer.close();
closePromise.then(() => {
events.push('closePromise');
});
abortPromise = writer.abort(error1);
abortPromise.then(() => {
events.push('abortPromise');
});
return Promise.all([
promise_rejects(t, new TypeError(), writer.close(),
'writer.close() must reject with an error indicating already closing'),
promise_rejects(t, new TypeError(), writer.ready, 'writer.ready must reject with an error indicating abort'),
flushAsyncEvents()
]);
}).then(() => {
assert_array_equals(events, [], 'closePromise, abortPromise and writer.closed must not be fulfilled/rejected yet');
controller.error(error2);
return Promise.all([
promise_rejects(t, new TypeError(), writer.close(),
'writer.close() must reject with an error indicating already closing'),
promise_rejects(t, new TypeError(), writer.ready,
'writer.ready must be still rejected with the error indicating abort'),
flushAsyncEvents()
]);
}).then(() => {
assert_array_equals(
events, [],
'closePromise, abortPromise and writer.closed must not be fulfilled/rejected yet even after ' +
'controller.error() call');
resolveClose();
return Promise.all([
closePromise,
abortPromise,
writer.closed,
flushAsyncEvents()
]);
}).then(() => {
assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
'closedPromise, abortPromise and writer.closed must fulfill');
return Promise.all([
promise_rejects(t, new TypeError(), writer.close(),
'writer.close() must reject with an error indicating already closing'),
promise_rejects(t, new TypeError(), writer.ready,
'writer.ready must be still rejected with the error indicating abort')
]);
}).then(() => {
writer.releaseLock();
return Promise.all([
promise_rejects(t, new TypeError(), writer.close(),
'writer.close() must reject with an error indicating release'),
promise_rejects(t, new TypeError(), writer.ready,
'writer.ready must be rejected with an error indicating release'),
promise_rejects(t, new TypeError(), writer.closed,
'writer.closed must be rejected with an error indicating release')
]);
});
}, 'writer.abort(), controller.error() while there is an in-flight close, and then finish the close');
promise_test(t => {
let resolveWrite;
let controller;
const ws = new WritableStream({
const ws = recordingWritableStream({
write(chunk, c) {
controller = c;
return new Promise(resolve => {
@ -750,8 +858,8 @@ promise_test(t => {
const writePromise2 = writer.write('a');
return Promise.all([
promise_rejects(t, new TypeError(), writePromise2,
'writePromise2 must reject with an error indicating the stream has already been errored'),
promise_rejects(t, error2, writePromise2,
'writePromise2 must reject with the error passed to the controller\'s error method'),
promise_rejects(t, error2, writer.ready,
'writer.ready must reject with the error passed to the controller\'s error method'),
flushAsyncEvents()
@ -767,34 +875,35 @@ promise_test(t => {
const writePromise3 = writer.write('a');
return Promise.all([
promise_rejects(t, error2, abortPromise,
'abortPromise must reject with the error passed to the controller\'s error method'),
promise_rejects(t, new TypeError(), writePromise3,
'writePromise3 must reject with an error indicating the stream has already been errored'),
promise_rejects(t, error2, writePromise3,
'writePromise3 must reject with the error passed to the controller\'s error method'),
flushAsyncEvents()
]);
}).then(() => {
assert_array_equals(
events, ['abortPromise'],
events, [],
'writePromise and writer.closed must not be fulfilled/rejected yet even after writer.abort()');
resolveWrite();
return Promise.all([
promise_rejects(t, error2, abortPromise,
'abort() must reject with the error passed to the controller\'s error method'),
promise_rejects(t, error2, writer.closed,
'writer.closed must reject with the error passed to the controller\'s error method'),
flushAsyncEvents()
]);
}).then(() => {
assert_array_equals(events, ['abortPromise', 'writePromise', 'closed'],
assert_array_equals(events, ['writePromise', 'abortPromise', 'closed'],
'writePromise, abortPromise and writer.closed must fulfill/reject');
assert_array_equals(ws.events, ['write', 'a'], 'sink abort() should not be called');
const writePromise4 = writer.write('a');
return Promise.all([
writePromise,
promise_rejects(t, new TypeError(), writePromise4,
'writePromise4 must reject with an error indicating that the stream has already been errored'),
promise_rejects(t, error2, writePromise4,
'writePromise4 must reject with the error passed to the controller\'s error method'),
promise_rejects(t, error2, writer.ready,
'writer.ready must be still rejected with the error passed to the controller\'s error method')
]);
@ -808,7 +917,84 @@ promise_test(t => {
'writer.closed must be rejected with an error indicating release')
]);
});
}, 'controller.error(), writer.abort() while there is a pending write, and then finish the write');
}, 'controller.error(), writer.abort() while there is an in-flight write, and then finish the write');
promise_test(t => {
let resolveClose;
let controller;
const ws = new WritableStream({
start(c) {
controller = c;
},
close() {
return new Promise(resolve => {
resolveClose = resolve;
});
}
});
let closePromise;
let abortPromise;
const events = [];
const writer = ws.getWriter();
writer.closed.then(() => {
events.push('closed');
});
// Wait for ws to start
return flushAsyncEvents().then(() => {
closePromise = writer.close();
closePromise.then(() => {
events.push('closePromise');
});
controller.error(error2);
return flushAsyncEvents();
}).then(() => {
assert_array_equals(events, [], 'closePromise must not be fulfilled/rejected yet');
abortPromise = writer.abort(error1);
abortPromise.then(() => {
events.push('abortPromise');
});
return Promise.all([
promise_rejects(t, error2, writer.ready,
'writer.ready must reject with the error passed to the controller\'s error method'),
flushAsyncEvents()
]);
}).then(() => {
assert_array_equals(
events, [],
'closePromise and writer.closed must not be fulfilled/rejected yet even after writer.abort()');
resolveClose();
return Promise.all([
closePromise,
promise_rejects(t, error2, writer.ready,
'writer.ready must be still rejected with the error passed to the controller\'s error method'),
writer.closed,
flushAsyncEvents()
]);
}).then(() => {
assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
'abortPromise, closePromise and writer.closed must fulfill/reject');
}).then(() => {
writer.releaseLock();
return Promise.all([
promise_rejects(t, new TypeError(), writer.ready,
'writer.ready must be rejected with an error indicating release'),
promise_rejects(t, new TypeError(), writer.closed,
'writer.closed must be rejected with an error indicating release')
]);
});
}, 'controller.error(), writer.abort() while there is an in-flight close, and then finish the close');
promise_test(t => {
let resolveWrite;
@ -833,6 +1019,7 @@ promise_test(t => {
});
}, 'releaseLock() while aborting should reject the original closed promise');
// TODO(ricea): Consider removing this test if it is no longer useful.
promise_test(t => {
let resolveWrite;
let resolveAbort;
@ -861,15 +1048,254 @@ promise_test(t => {
resolveWrite();
return abortStarted.then(() => {
writer.releaseLock();
assert_not_equals(writer.closed, closed, 'closed promise should have changed');
assert_equals(writer.closed, closed, 'closed promise should not have changed');
resolveAbort();
return Promise.all([
writePromise,
abortPromise,
promise_rejects(t, new TypeError(), closed, 'original closed should reject'),
promise_rejects(t, new TypeError(), writer.closed, 'new closed should reject')]);
promise_rejects(t, new TypeError(), closed, 'closed should reject')]);
});
});
}, 'releaseLock() during delayed async abort() should create a new rejected closed promise');
}, 'releaseLock() during delayed async abort() should reject the writer.closed promise');
promise_test(() => {
let resolveStart;
const ws = recordingWritableStream({
start() {
return new Promise(resolve => {
resolveStart = resolve;
});
}
});
const abortPromise = ws.abort('done');
return flushAsyncEvents().then(() => {
assert_array_equals(ws.events, [], 'abort() should not be called during start()');
resolveStart();
return abortPromise.then(() => {
assert_array_equals(ws.events, ['abort', 'done'], 'abort() should be called after start() is done');
});
});
}, 'sink abort() should not be called until sink start() is done');
promise_test(() => {
let resolveStart;
let controller;
const ws = recordingWritableStream({
start(c) {
controller = c;
return new Promise(resolve => {
resolveStart = resolve;
});
}
});
const abortPromise = ws.abort('done');
controller.error(error1);
resolveStart();
return abortPromise.then(() =>
assert_array_equals(ws.events, ['abort', 'done'],
'abort() should still be called if start() errors the controller'));
}, 'if start attempts to error the controller after abort() has been called, then it should lose');
promise_test(() => {
const ws = recordingWritableStream({
start() {
return Promise.reject(error1);
}
});
return ws.abort('done').then(() =>
assert_array_equals(ws.events, ['abort', 'done'], 'abort() should still be called if start() rejects'));
}, 'stream abort() promise should still resolve if sink start() rejects');
promise_test(t => {
const ws = new WritableStream();
const writer = ws.getWriter();
const writerReady1 = writer.ready;
writer.abort('a');
const writerReady2 = writer.ready;
assert_not_equals(writerReady1, writerReady2, 'abort() should replace the ready promise with a rejected one');
return Promise.all([writerReady1,
promise_rejects(t, new TypeError(), writerReady2, 'writerReady2 should reject')]);
}, 'writer abort() during sink start() should replace the writer.ready promise synchronously');
promise_test(t => {
const events = [];
const ws = recordingWritableStream();
const writer = ws.getWriter();
const writePromise1 = writer.write(1);
const abortPromise = writer.abort('a');
const writePromise2 = writer.write(2);
const closePromise = writer.close();
writePromise1.catch(() => events.push('write1'));
abortPromise.then(() => events.push('abort'));
writePromise2.catch(() => events.push('write2'));
closePromise.catch(() => events.push('close'));
return Promise.all([
promise_rejects(t, new TypeError(), writePromise1, 'first write() should reject'),
abortPromise,
promise_rejects(t, new TypeError(), writePromise2, 'second write() should reject'),
promise_rejects(t, new TypeError(), closePromise, 'close() should reject')
])
.then(() => {
assert_array_equals(events, ['write2', 'write1', 'abort', 'close'],
'promises should resolve in the standard order');
assert_array_equals(ws.events, ['abort', 'a'], 'underlying sink write() should not be called');
});
}, 'promises returned from other writer methods should be rejected when writer abort() happens during sink start()');
promise_test(t => {
let writeReject;
let controller;
const ws = new WritableStream({
write(chunk, c) {
controller = c;
return new Promise((resolve, reject) => {
writeReject = reject;
});
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
const writePromise = writer.write('a');
const abortPromise = writer.abort();
controller.error(error1);
writeReject(error2);
return Promise.all([
promise_rejects(t, error2, writePromise, 'write() should reject with error2'),
abortPromise
]);
});
}, 'abort() should succeed despite rejection from write');
promise_test(t => {
let closeReject;
let controller;
const ws = new WritableStream({
start(c) {
controller = c;
},
close() {
return new Promise((resolve, reject) => {
closeReject = reject;
});
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
const closePromise = writer.close();
const abortPromise = writer.abort();
controller.error(error1);
closeReject(error2);
return Promise.all([
promise_rejects(t, error2, closePromise, 'close() should reject with error2'),
promise_rejects(t, error2, abortPromise, 'abort() should reject with error2')
]);
});
}, 'abort() should be rejected with the rejection returned from close()');
promise_test(t => {
let rejectWrite;
const ws = recordingWritableStream({
write() {
return new Promise((resolve, reject) => {
rejectWrite = reject;
});
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
const writePromise = writer.write('1');
const abortPromise = writer.abort(error2);
rejectWrite(error1);
return Promise.all([
promise_rejects(t, error1, writePromise, 'write should reject'),
abortPromise,
promise_rejects(t, new TypeError(), writer.closed, 'closed should reject with TypeError')
]);
}).then(() => {
assert_array_equals(ws.events, ['write', '1', 'abort', error2], 'abort sink method should be called');
});
}, 'a rejecting sink.write() should not prevent sink.abort() from being called');
promise_test(() => {
const ws = recordingWritableStream({
start() {
return Promise.reject(error1);
}
});
return ws.abort(error2)
.then(() => {
assert_array_equals(ws.events, ['abort', error2]);
});
}, 'when start errors after stream abort(), underlying sink abort() should be called anyway');
promise_test(t => {
const ws = new WritableStream();
const abortPromise1 = ws.abort();
const abortPromise2 = ws.abort();
return Promise.all([
abortPromise1,
promise_rejects(t, new TypeError(), abortPromise2, 'second abort() should reject')
]);
}, 'when calling abort() twice on the same stream, the second call should reject');
promise_test(t => {
let controller;
let resolveWrite;
const ws = recordingWritableStream({
start(c) {
controller = c;
},
write() {
return new Promise(resolve => {
resolveWrite = resolve;
});
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
const writePromise = writer.write('chunk');
controller.error(error1);
const abortPromise = writer.abort(error2);
resolveWrite();
return Promise.all([
writePromise,
promise_rejects(t, error1, abortPromise, 'abort() should reject')
]).then(() => {
assert_array_equals(ws.events, ['write', 'chunk'], 'sink abort() should not be called');
});
});
}, 'sink abort() should not be called if stream was erroring due to controller.error() before abort() was called');
promise_test(t => {
let resolveWrite;
let size = 1;
const ws = recordingWritableStream({
write() {
return new Promise(resolve => {
resolveWrite = resolve;
});
}
}, {
size() {
return size;
},
highWaterMark: 1
});
const writer = ws.getWriter();
return writer.ready.then(() => {
const writePromise1 = writer.write('chunk1');
size = NaN;
const writePromise2 = writer.write('chunk2');
const abortPromise = writer.abort(error2);
resolveWrite();
return Promise.all([
writePromise1,
promise_rejects(t, new RangeError(), writePromise2, 'second write() should reject'),
promise_rejects(t, new RangeError(), abortPromise, 'abort() should reject')
]).then(() => {
assert_array_equals(ws.events, ['write', 'chunk1'], 'sink abort() should not be called');
});
});
}, 'sink abort() should not be called if stream was erroring due to bad strategy before abort() was called');
done();

View file

@ -61,7 +61,7 @@ promise_test(t => {
const writer = ws.getWriter();
const p1 = promise_rejects(t, new TypeError(), writer.write('a'), 'write should reject with a TypeError');
const p1 = promise_rejects(t, error1, writer.write('a'), 'write should reject with the thrown error');
const p2 = promise_rejects(t, error1, writer.closed, 'closed should reject with the thrown error');

View file

@ -26,41 +26,70 @@ promise_test(() => {
}, 'fulfillment value of ws.close() call must be undefined even if the underlying sink returns a non-undefined ' +
'value');
promise_test(t => {
const passedError = new Error('error me');
promise_test(() => {
let controller;
let resolveClose;
const ws = new WritableStream({
close(c) {
start(c) {
controller = c;
return delay(50);
},
close() {
return new Promise(resolve => {
resolveClose = resolve;
});
}
});
const writer = ws.getWriter();
return Promise.all([
writer.close(),
delay(10).then(() => controller.error(passedError)),
promise_rejects(t, passedError, writer.closed,
'closed promise should be rejected with the passed error'),
delay(70).then(() => promise_rejects(t, passedError, writer.closed, 'closed should stay rejected'))
]);
}, 'when sink calls error asynchronously while closing, the stream should become errored');
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(t => {
promise_test(() => {
let controller;
const passedError = new Error('error me');
const ws = new WritableStream({
close(controller) {
start(c) {
controller = c;
},
close() {
controller.error(passedError);
}
});
const writer = ws.getWriter();
return writer.close().then(() => promise_rejects(t, passedError, writer.closed, 'closed should stay rejected'));
}, 'when sink calls error synchronously while closing, the stream should become errored');
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(t, error1, writer.close(), 'close() must reject with the error'),
promise_rejects(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);
@ -76,9 +105,13 @@ promise_test(t => {
});
}, 'releaseLock on a stream with a pending write in which the stream has been errored');
promise_test(t => {
promise_test(() => {
let controller;
const ws = new WritableStream({
close(controller) {
start(c) {
controller = c;
},
close() {
controller.error(error1);
return new Promise(() => {});
}
@ -90,7 +123,7 @@ promise_test(t => {
return delay(0).then(() => {
writer.releaseLock();
});
}, 'releaseLock on a stream with a pending close in which the stream has been errored');
}, 'releaseLock on a stream with a pending close in which controller.error() was called');
promise_test(() => {
const ws = recordingWritableStream();
@ -276,7 +309,7 @@ promise_test(() => {
});
}, 'promises must fulfill/reject in the expected order on closure');
promise_test(t => {
promise_test(() => {
const ws = new WritableStream({});
// Wait until the WritableStream starts so that the close() call gets processed. Otherwise, abort() will be
@ -295,8 +328,7 @@ promise_test(t => {
abortPromise.then(() => {
events.push('abortPromise');
}),
promise_rejects(t, new TypeError(), writer.closed, 'writer.closed must reject with an error indicating abort')
.then(() => {
writer.closed.then(() => {
events.push('closed');
})
]).then(() => {
@ -321,22 +353,16 @@ promise_test(t => {
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(t, error1, closePromise,
'closePromise must reject with the error returned from the sink\'s close method')
.then(() => {
events.push('closePromise');
}),
'closePromise must reject with the error returned from the sink\'s close method'),
promise_rejects(t, error1, abortPromise,
'abortPromise must reject with the error returned from the sink\'s close method')
.then(() => {
events.push('abortPromise');
}),
promise_rejects(t, error1, writer.closed,
'writer.closed must reject with the error returned from the sink\'s close method')
.then(() => {
events.push('closed');
})
'abortPromise must reject with the error returned from the sink\'s close method'),
promise_rejects(t, new TypeError(), writer.closed,
'writer.closed must reject with a TypeError indicating the stream was aborted')
]).then(() => {
assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
'promises must fulfill/reject in the expected order');
@ -344,4 +370,37 @@ promise_test(t => {
});
}, '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(t, error1, closePromise, 'close() should reject')
]).then(() => {
assert_true(closeRejected);
});
});
});
}, 'close() should not reject until no sink methods are in flight');
done();

View file

@ -37,24 +37,23 @@ promise_test(t => {
return Promise.all([
writer.write('a'),
promise_rejects(t, error1, writer.closed, 'controller.error() in write() should errored the stream')
promise_rejects(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(controller) {
controller.error(error1);
close(...args) {
t.step(() => {
assert_array_equals(args, [], 'no arguments should be passed to close');
});
}
});
const writer = ws.getWriter();
return Promise.all([
writer.close(),
promise_rejects(t, error1, writer.closed, 'controller.error() in close() should error the stream')
]);
}, 'controller argument should be passed to close method');
return ws.getWriter().close();
}, 'controller argument should not be passed to close method');
promise_test(() => {
const ws = new WritableStream({}, {

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>error.js dedicated worker wrapper file</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
'use strict';
fetch_tests_from_worker(new Worker('error.js'));
</script>

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>error.js browser context wrapper file</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="error.js"></script>

View file

@ -0,0 +1,69 @@
'use strict';
if (self.importScripts) {
self.importScripts('/resources/testharness.js');
}
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(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(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');
done();

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>error.js service worker wrapper file</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script>
'use strict';
service_worker_test('error.js', 'Service worker test setup');
</script>

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>error.js shared worker wrapper file</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
'use strict';
fetch_tests_from_worker(new SharedWorker('error.js'));
</script>

View file

@ -226,4 +226,25 @@ promise_test(() => {
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');
done();

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>properties.js dedicated worker wrapper file</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
'use strict';
fetch_tests_from_worker(new Worker('properties.js'));
</script>

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>properties.js browser context wrapper file</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="properties.js"></script>

View file

@ -0,0 +1,219 @@
'use strict';
if (self.importScripts) {
self.importScripts('/resources/testharness.js');
}
// The purpose of this file is to test for objects, attributes and arguments that should not exist.
// The test cases are generated from data tables to reduce duplication.
// Courtesy of André Bargull. Source is https://esdiscuss.org/topic/isconstructor#content-11.
function IsConstructor(o) {
try {
new new Proxy(o, { construct: () => ({}) })();
return true;
} catch (e) {
return false;
}
}
for (const func of ['WritableStreamDefaultController', 'WritableStreamDefaultWriter']) {
test(() => {
assert_equals(self[func], undefined, `${func} should not be defined`);
}, `${func} should not be exported on the global object`);
}
// Now get hold of the symbols so we can test their properties.
self.WritableStreamDefaultController = (() => {
let controller;
new WritableStream({
start(c) {
controller = c;
}
});
return controller.constructor;
})();
self.WritableStreamDefaultWriter = new WritableStream().getWriter().constructor;
const expected = {
WritableStream: {
constructor: {
type: 'constructor',
length: 0
},
locked: {
type: 'getter'
},
abort: {
type: 'method',
length: 1
},
getWriter: {
type: 'method',
length: 0
}
},
WritableStreamDefaultController: {
constructor: {
type: 'constructor',
length: 4
},
error: {
type: 'method',
length: 1
}
},
WritableStreamDefaultWriter: {
constructor: {
type: 'constructor',
length: 1
},
closed: {
type: 'getter'
},
desiredSize: {
type: 'getter'
},
ready: {
type: 'getter'
},
abort: {
type: 'method',
length: 1
},
close: {
type: 'method',
length: 0
},
releaseLock: {
type: 'method',
length: 0
},
write: {
type: 'method',
length: 1
}
}
};
for (const c in expected) {
const properties = expected[c];
const prototype = self[c].prototype;
for (const name in properties) {
const fullName = `${c}.prototype.${name}`;
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
test(() => {
const { configurable, enumerable } = descriptor;
assert_true(configurable, `${name} should be configurable`);
assert_false(enumerable, `${name} should not be enumerable`);
}, `${fullName} should have standard properties`);
const type = properties[name].type;
switch (type) {
case 'getter':
test(() => {
const { writable, get, set } = descriptor;
assert_equals(writable, undefined, `${name} should not be a data descriptor`);
assert_equals(typeof get, 'function', `${name} should have a getter`);
assert_equals(set, undefined, `${name} should not have a setter`);
}, `${fullName} should be a getter`);
break;
case 'constructor':
case 'method':
test(() => {
assert_true(descriptor.writable, `${name} should be writable`);
assert_equals(typeof prototype[name], 'function', `${name} should be a function`);
assert_equals(prototype[name].length, properties[name].length,
`${name} should take ${properties[name].length} arguments`);
if (type === 'constructor') {
assert_true(IsConstructor(prototype[name]), `${name} should be a constructor`);
} else {
assert_false(IsConstructor(prototype[name]), `${name} should not be a constructor`);
}
}, `${fullName} should be a ${type}`);
break;
}
}
test(() => {
const expectedPropertyNames = Object.keys(properties).sort();
const actualPropertyNames = Object.getOwnPropertyNames(prototype).sort();
assert_array_equals(actualPropertyNames, expectedPropertyNames,
`${c} properties should match expected properties`);
}, `${c}.prototype should have exactly the expected properties`);
}
const sinkMethods = {
start: {
length: 1,
trigger: () => {}
},
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`);
if (method !== 'start') {
promise_test(t => {
const unreachedTraps = ['getPrototypeOf', 'setPrototypeOf', 'isExtensible', 'preventExtensions',
'getOwnPropertyDescriptor', 'defineProperty', 'has', 'set', 'deleteProperty', 'ownKeys',
'apply', 'construct'];
const handler = {
get: t.step_func((target, property) => {
if (property === 'type') {
return undefined;
}
assert_in_array(property, ['start', method], `only start() and ${method}() should be called`);
return () => Promise.resolve();
})
};
for (const trap of unreachedTraps) {
handler[trap] = t.unreached_func(`${trap} should not be trapped`);
}
const sink = new Proxy({}, handler);
const ws = new WritableStream(sink);
return trigger(ws.getWriter());
}, `unexpected properties should not be accessed when calling sink method ${method}`);
}
}
done();

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>properties.js service worker wrapper file</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script>
'use strict';
service_worker_test('properties.js', 'Service worker test setup');
</script>

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>properties.js shared worker wrapper file</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
'use strict';
fetch_tests_from_worker(new SharedWorker('properties.js'));
</script>

View file

@ -78,8 +78,8 @@ promise_test(t => {
const resolved = [];
const writer = ws.getWriter();
const readyPromise1 = writer.ready.then(() => resolved.push('ready1'));
const writePromise = promise_rejects(t, new TypeError(), writer.write(),
'write() should reject with a TypeError')
const writePromise = promise_rejects(t, error1, writer.write(),
'write() should reject with the error')
.then(() => resolved.push('write'));
const readyPromise2 = promise_rejects(t, error1, writer.ready, 'ready should reject with error1')
.then(() => resolved.push('ready2'));

View file

@ -6,6 +6,8 @@ if (self.importScripts) {
self.importScripts('../resources/recording-streams.js');
}
const error1 = { name: 'error1' };
promise_test(() => {
let resolveStartPromise;
const ws = recordingWritableStream({
@ -93,15 +95,74 @@ promise_test(() => {
}, 'underlying sink\'s write or close should not be invoked if the promise returned by start is rejected');
promise_test(t => {
const rejection = { name: 'this is checked' };
const ws = new WritableStream({
start() {
return {
then(onFulfilled, onRejected) { onRejected(rejection); }
then(onFulfilled, onRejected) { onRejected(error1); }
};
}
});
return promise_rejects(t, rejection, ws.getWriter().closed, 'closed promise should be rejected');
return promise_rejects(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(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(t, error1, writePromise, 'write() should fail'),
promise_rejects(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();
}
}, new CountQueuingStrategy({ 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');
done();

View file

@ -191,8 +191,8 @@ promise_test(t => {
'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');
}, 'writer.write(), ready and closed reject with the error passed to controller.error() made before sink.write' +
' rejection');
promise_test(() => {
const numberOfWrites = 1000;