mirror of
https://github.com/servo/servo.git
synced 2025-06-23 08:34:42 +01:00
1301 lines
41 KiB
JavaScript
1301 lines
41 KiB
JavaScript
'use strict';
|
|
|
|
if (self.importScripts) {
|
|
self.importScripts('/resources/testharness.js');
|
|
self.importScripts('../resources/test-utils.js');
|
|
self.importScripts('../resources/recording-streams.js');
|
|
}
|
|
|
|
const error1 = new Error('error1');
|
|
error1.name = 'error1';
|
|
|
|
const error2 = new Error('error2');
|
|
error2.name = 'error2';
|
|
|
|
promise_test(t => {
|
|
const ws = new WritableStream({
|
|
write: t.unreached_func('write() should not be called')
|
|
});
|
|
|
|
const writer = ws.getWriter();
|
|
const writePromise = writer.write('a');
|
|
|
|
const readyPromise = writer.ready;
|
|
|
|
writer.abort(error1);
|
|
|
|
assert_equals(writer.ready, readyPromise, 'the ready promise property should not change');
|
|
|
|
return Promise.all([
|
|
promise_rejects(t, new TypeError(), readyPromise, 'the ready promise should reject with a TypeError'),
|
|
promise_rejects(t, new TypeError(), writePromise, 'the write() promise should reject with a TypeError')
|
|
]);
|
|
}, 'Aborting a WritableStream before it starts should cause the writer\'s unsettled ready promise to reject');
|
|
|
|
promise_test(t => {
|
|
const ws = new WritableStream();
|
|
|
|
const writer = ws.getWriter();
|
|
writer.write('a');
|
|
|
|
const readyPromise = writer.ready;
|
|
|
|
return readyPromise.then(() => {
|
|
writer.abort(error1);
|
|
|
|
assert_not_equals(writer.ready, readyPromise, 'the ready promise property should change');
|
|
return promise_rejects(t, new TypeError(), writer.ready, 'the ready promise should reject with a TypeError');
|
|
});
|
|
}, 'Aborting a WritableStream should cause the writer\'s fulfilled ready promise to reset to a rejected one');
|
|
|
|
promise_test(t => {
|
|
const ws = new WritableStream();
|
|
const writer = ws.getWriter();
|
|
|
|
writer.releaseLock();
|
|
|
|
return promise_rejects(t, new TypeError(), writer.abort(), 'abort() should reject with a TypeError');
|
|
}, 'abort() on a released writer rejects');
|
|
|
|
promise_test(t => {
|
|
const ws = recordingWritableStream();
|
|
|
|
return delay(0)
|
|
.then(() => {
|
|
const writer = ws.getWriter();
|
|
|
|
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'),
|
|
abortPromise
|
|
]);
|
|
})
|
|
.then(() => {
|
|
assert_array_equals(ws.events, ['abort', undefined]);
|
|
});
|
|
}, 'Aborting a WritableStream immediately prevents future writes');
|
|
|
|
promise_test(t => {
|
|
const ws = recordingWritableStream();
|
|
const results = [];
|
|
|
|
return delay(0)
|
|
.then(() => {
|
|
const writer = ws.getWriter();
|
|
|
|
results.push(
|
|
writer.write(1),
|
|
promise_rejects(t, new TypeError(), writer.write(2), 'write(2) must reject with a TypeError'),
|
|
promise_rejects(t, new TypeError(), writer.write(3), 'write(3) must reject with a TypeError')
|
|
);
|
|
|
|
const abortPromise = writer.abort();
|
|
|
|
results.push(
|
|
promise_rejects(t, new TypeError(), writer.write(4), 'write(4) must reject with a TypeError'),
|
|
promise_rejects(t, new TypeError(), writer.write(5), 'write(5) must reject with a TypeError')
|
|
);
|
|
|
|
return abortPromise;
|
|
}).then(() => {
|
|
assert_array_equals(ws.events, ['write', 1, 'abort', undefined]);
|
|
|
|
return Promise.all(results);
|
|
});
|
|
}, 'Aborting a WritableStream prevents further writes after any that are in progress');
|
|
|
|
promise_test(() => {
|
|
const ws = new WritableStream({
|
|
abort() {
|
|
return 'Hello';
|
|
}
|
|
});
|
|
const writer = ws.getWriter();
|
|
|
|
return writer.abort('a').then(value => {
|
|
assert_equals(value, undefined, 'fulfillment value must be undefined');
|
|
});
|
|
}, 'Fulfillment value of ws.abort() call must be undefined even if the underlying sink returns a non-undefined value');
|
|
|
|
promise_test(t => {
|
|
const ws = new WritableStream({
|
|
abort() {
|
|
throw error1;
|
|
}
|
|
});
|
|
const writer = ws.getWriter();
|
|
|
|
return promise_rejects(t, error1, writer.abort(undefined),
|
|
'rejection reason of abortPromise must be the error thrown by abort');
|
|
}, 'WritableStream if sink\'s abort throws, the promise returned by writer.abort() rejects');
|
|
|
|
promise_test(t => {
|
|
const ws = new WritableStream({
|
|
abort() {
|
|
throw error1;
|
|
}
|
|
});
|
|
|
|
return promise_rejects(t, error1, ws.abort(undefined),
|
|
'rejection reason of abortPromise must be the error thrown by abort');
|
|
}, 'WritableStream if sink\'s abort throws, the promise returned by ws.abort() rejects');
|
|
|
|
promise_test(t => {
|
|
let resolveWritePromise;
|
|
const ws = new WritableStream({
|
|
write() {
|
|
return new Promise(resolve => {
|
|
resolveWritePromise = resolve;
|
|
});
|
|
},
|
|
abort() {
|
|
throw error1;
|
|
}
|
|
});
|
|
|
|
const writer = ws.getWriter();
|
|
|
|
writer.write().catch(() => {});
|
|
return flushAsyncEvents().then(() => {
|
|
const abortPromise = writer.abort(undefined);
|
|
|
|
resolveWritePromise();
|
|
return promise_rejects(t, error1, abortPromise,
|
|
'rejection reason of abortPromise must be the error thrown by abort');
|
|
});
|
|
}, 'WritableStream if sink\'s abort throws, for an abort performed during a write, the promise returned by ' +
|
|
'ws.abort() rejects');
|
|
|
|
promise_test(() => {
|
|
const ws = recordingWritableStream();
|
|
const writer = ws.getWriter();
|
|
|
|
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();
|
|
|
|
const abortPromise = writer.abort(error1);
|
|
|
|
const events = [];
|
|
writer.ready.catch(() => {
|
|
events.push('ready');
|
|
});
|
|
writer.closed.catch(() => {
|
|
events.push('closed');
|
|
});
|
|
|
|
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'),
|
|
promise_rejects(t, new TypeError(), writer.ready, 'ready should reject with a TypeError'),
|
|
promise_rejects(t, new TypeError(), writer.closed, 'closed should reject with a TypeError')
|
|
]).then(() => {
|
|
assert_array_equals(['ready', 'closed'], events, 'ready should reject before closed');
|
|
});
|
|
}, 'Aborting a WritableStream puts it in an errored state, with a TypeError as the stored error');
|
|
|
|
promise_test(t => {
|
|
const ws = new WritableStream();
|
|
const writer = ws.getWriter();
|
|
|
|
const writePromise = promise_rejects(t, new TypeError(), writer.write('a'),
|
|
'writing should reject with a TypeError');
|
|
|
|
writer.abort(error1);
|
|
|
|
return writePromise;
|
|
}, 'Aborting a WritableStream causes any outstanding write() promises to be rejected with a TypeError');
|
|
|
|
promise_test(t => {
|
|
const ws = recordingWritableStream();
|
|
const writer = ws.getWriter();
|
|
|
|
const closePromise = writer.close();
|
|
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'),
|
|
abortPromise
|
|
]).then(() => {
|
|
assert_array_equals(ws.events, ['abort', error1]);
|
|
});
|
|
}, 'Closing but then immediately aborting a WritableStream causes the stream to error');
|
|
|
|
promise_test(() => {
|
|
let resolveClose;
|
|
const ws = new WritableStream({
|
|
close() {
|
|
return new Promise(resolve => {
|
|
resolveClose = resolve;
|
|
});
|
|
}
|
|
});
|
|
const writer = ws.getWriter();
|
|
|
|
const closePromise = writer.close();
|
|
|
|
return delay(0).then(() => {
|
|
const abortPromise = writer.abort(error1);
|
|
resolveClose();
|
|
return Promise.all([
|
|
writer.closed,
|
|
abortPromise,
|
|
closePromise
|
|
]);
|
|
});
|
|
}, 'Closing a WritableStream and aborting it while it closes causes the stream to ignore the abort attempt');
|
|
|
|
promise_test(() => {
|
|
const ws = new WritableStream();
|
|
const writer = ws.getWriter();
|
|
|
|
writer.close();
|
|
|
|
return delay(0).then(() => writer.abort());
|
|
}, 'Aborting a WritableStream after it is closed is a no-op');
|
|
|
|
promise_test(t => {
|
|
// Testing that per https://github.com/whatwg/streams/issues/620#issuecomment-263483953 the fallback to close was
|
|
// removed.
|
|
|
|
// Cannot use recordingWritableStream since it always has an abort
|
|
let closeCalled = false;
|
|
const ws = new WritableStream({
|
|
close() {
|
|
closeCalled = true;
|
|
}
|
|
});
|
|
|
|
const writer = ws.getWriter();
|
|
|
|
writer.abort();
|
|
|
|
return promise_rejects(t, new TypeError(), writer.closed, 'closed should reject with a TypeError').then(() => {
|
|
assert_false(closeCalled, 'close must not have been called');
|
|
});
|
|
}, 'WritableStream should NOT call underlying sink\'s close if no abort is supplied (historical)');
|
|
|
|
promise_test(() => {
|
|
let thenCalled = false;
|
|
const ws = new WritableStream({
|
|
abort() {
|
|
return {
|
|
then(onFulfilled) {
|
|
thenCalled = true;
|
|
onFulfilled();
|
|
}
|
|
};
|
|
}
|
|
});
|
|
const writer = ws.getWriter();
|
|
return writer.abort().then(() => assert_true(thenCalled, 'then() should be called'));
|
|
}, 'returning a thenable from abort() should work');
|
|
|
|
promise_test(t => {
|
|
const ws = new WritableStream({
|
|
write() {
|
|
return flushAsyncEvents();
|
|
}
|
|
});
|
|
const writer = ws.getWriter();
|
|
return writer.ready.then(() => {
|
|
const writePromise = writer.write('a');
|
|
writer.abort(error1);
|
|
let closedRejected = false;
|
|
return Promise.all([
|
|
writePromise.then(() => assert_false(closedRejected, '.closed should not resolve before write()')),
|
|
promise_rejects(t, new TypeError(), writer.closed, '.closed should reject').then(() => {
|
|
closedRejected = true;
|
|
})
|
|
]);
|
|
});
|
|
}, '.closed should not resolve before fulfilled write()');
|
|
|
|
promise_test(t => {
|
|
const ws = new WritableStream({
|
|
write() {
|
|
return Promise.reject(error1);
|
|
}
|
|
});
|
|
const writer = ws.getWriter();
|
|
return writer.ready.then(() => {
|
|
const writePromise = writer.write('a');
|
|
const abortPromise = writer.abort(error2);
|
|
let closedRejected = false;
|
|
return Promise.all([
|
|
promise_rejects(t, error1, writePromise, 'write() should reject')
|
|
.then(() => assert_false(closedRejected, '.closed should not resolve before write()')),
|
|
promise_rejects(t, new TypeError(), writer.closed, '.closed should reject')
|
|
.then(() => {
|
|
closedRejected = true;
|
|
}),
|
|
abortPromise
|
|
]);
|
|
});
|
|
}, '.closed should not resolve before rejected write(); write() error should not overwrite abort() error');
|
|
|
|
promise_test(t => {
|
|
const ws = new WritableStream({
|
|
write() {
|
|
return flushAsyncEvents();
|
|
}
|
|
}, new CountQueuingStrategy(4));
|
|
const writer = ws.getWriter();
|
|
return writer.ready.then(() => {
|
|
const settlementOrder = [];
|
|
return Promise.all([
|
|
writer.write('1').then(() => settlementOrder.push(1)),
|
|
promise_rejects(t, new TypeError(), writer.write('2'), 'first queued write should be rejected')
|
|
.then(() => settlementOrder.push(2)),
|
|
promise_rejects(t, new TypeError(), writer.write('3'), 'second queued write should be rejected')
|
|
.then(() => settlementOrder.push(3)),
|
|
writer.abort(error1)
|
|
]).then(() => assert_array_equals([1, 2, 3], settlementOrder, 'writes should be satisfied in order'));
|
|
});
|
|
}, 'writes should be satisfied in order when aborting');
|
|
|
|
promise_test(t => {
|
|
const ws = new WritableStream({
|
|
write() {
|
|
return Promise.reject(error1);
|
|
}
|
|
}, new CountQueuingStrategy(4));
|
|
const writer = ws.getWriter();
|
|
return writer.ready.then(() => {
|
|
const settlementOrder = [];
|
|
return Promise.all([
|
|
promise_rejects(t, error1, writer.write('1'), 'in-flight write should be rejected')
|
|
.then(() => settlementOrder.push(1)),
|
|
promise_rejects(t, new TypeError(), writer.write('2'), 'first queued write should be rejected')
|
|
.then(() => settlementOrder.push(2)),
|
|
promise_rejects(t, new TypeError(), writer.write('3'), 'second queued write should be rejected')
|
|
.then(() => settlementOrder.push(3)),
|
|
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');
|
|
|
|
promise_test(t => {
|
|
const ws = new WritableStream({
|
|
write() {
|
|
return Promise.reject(error1);
|
|
}
|
|
});
|
|
const writer = ws.getWriter();
|
|
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, new TypeError(), writer.close(),
|
|
'writer.close() should reject with error from underlying write()'),
|
|
writer.abort()
|
|
]);
|
|
});
|
|
}, 'close() should reject with TypeError when abort() is first error');
|
|
|
|
promise_test(() => {
|
|
let resolveWrite;
|
|
const ws = recordingWritableStream({
|
|
write() {
|
|
return new Promise(resolve => {
|
|
resolveWrite = resolve;
|
|
});
|
|
}
|
|
});
|
|
|
|
const writer = ws.getWriter();
|
|
return writer.ready.then(() => {
|
|
writer.write('a');
|
|
const abortPromise = writer.abort('b');
|
|
return flushAsyncEvents().then(() => {
|
|
assert_array_equals(ws.events, ['write', 'a'], 'abort should not be called while write is in-flight');
|
|
resolveWrite();
|
|
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;
|
|
const ws = recordingWritableStream({
|
|
close() {
|
|
return new Promise(resolve => {
|
|
resolveClose = resolve;
|
|
});
|
|
}
|
|
});
|
|
|
|
const writer = ws.getWriter();
|
|
return writer.ready.then(() => {
|
|
writer.close();
|
|
const abortPromise = writer.abort();
|
|
return flushAsyncEvents().then(() => {
|
|
assert_array_equals(ws.events, ['close'], 'abort should not be called while close is in-flight');
|
|
resolveClose();
|
|
return abortPromise.then(() => {
|
|
assert_array_equals(ws.events, ['close'], 'abort should not be called');
|
|
});
|
|
});
|
|
});
|
|
}, 'underlying abort() should not be called if underlying close() has started');
|
|
|
|
promise_test(t => {
|
|
let rejectClose;
|
|
let abortCalled = false;
|
|
const ws = new WritableStream({
|
|
close() {
|
|
return new Promise((resolve, reject) => {
|
|
rejectClose = reject;
|
|
});
|
|
},
|
|
abort() {
|
|
abortCalled = true;
|
|
}
|
|
});
|
|
|
|
const writer = ws.getWriter();
|
|
return writer.ready.then(() => {
|
|
const closePromise = writer.close();
|
|
const abortPromise = writer.abort();
|
|
return flushAsyncEvents().then(() => {
|
|
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');
|
|
}).then(() => {
|
|
assert_false(abortCalled, 'underlying abort should not be called after close completes');
|
|
});
|
|
});
|
|
});
|
|
}, 'if underlying close() has started and then rejects, the abort() and close() promises should reject with the ' +
|
|
'underlying close rejection reason');
|
|
|
|
promise_test(t => {
|
|
let resolveWrite;
|
|
const ws = recordingWritableStream({
|
|
write() {
|
|
return new Promise(resolve => {
|
|
resolveWrite = resolve;
|
|
});
|
|
}
|
|
});
|
|
|
|
const writer = ws.getWriter();
|
|
return writer.ready.then(() => {
|
|
writer.write('a');
|
|
const closePromise = writer.close();
|
|
const abortPromise = writer.abort('b');
|
|
|
|
return flushAsyncEvents().then(() => {
|
|
assert_array_equals(ws.events, ['write', 'a'], 'abort should not be called while write is in-flight');
|
|
resolveWrite();
|
|
return abortPromise.then(() => {
|
|
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');
|
|
});
|
|
});
|
|
});
|
|
}, '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();
|
|
const writer = ws.getWriter();
|
|
return writer.ready.then(() => {
|
|
const closePromise = writer.close();
|
|
const abortPromise = writer.abort();
|
|
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');
|
|
|
|
promise_test(t => {
|
|
const ws = new WritableStream({
|
|
write(chunk, controller) {
|
|
controller.error(error1);
|
|
return new Promise(() => {});
|
|
}
|
|
});
|
|
const writer = ws.getWriter();
|
|
return writer.ready.then(() => {
|
|
writer.write('a');
|
|
return promise_rejects(t, error1, writer.ready, 'writer.ready should reject');
|
|
});
|
|
}, 'writer.ready should reject on controller error without waiting for underlying write');
|
|
|
|
promise_test(t => {
|
|
let rejectWrite;
|
|
const ws = new WritableStream({
|
|
write() {
|
|
return new Promise((resolve, reject) => {
|
|
rejectWrite = reject;
|
|
});
|
|
}
|
|
});
|
|
|
|
let writePromise;
|
|
let abortPromise;
|
|
|
|
const events = [];
|
|
|
|
const writer = ws.getWriter();
|
|
|
|
writer.closed.catch(() => {
|
|
events.push('closed');
|
|
});
|
|
|
|
// Wait for ws to start
|
|
return flushAsyncEvents().then(() => {
|
|
writePromise = writer.write('a');
|
|
writePromise.catch(() => {
|
|
events.push('writePromise');
|
|
});
|
|
|
|
abortPromise = writer.abort(error1);
|
|
abortPromise.then(() => {
|
|
events.push('abortPromise');
|
|
});
|
|
|
|
const writePromise2 = writer.write('a');
|
|
|
|
return Promise.all([
|
|
promise_rejects(t, new TypeError(), writePromise2, 'writePromise2 must reject with an error indicating abort'),
|
|
promise_rejects(t, new TypeError(), writer.ready, 'writer.ready must reject with an error indicating abort'),
|
|
flushAsyncEvents()
|
|
]);
|
|
}).then(() => {
|
|
assert_array_equals(events, [], 'writePromise, abortPromise and writer.closed must not be rejected yet');
|
|
|
|
rejectWrite(error2);
|
|
|
|
return Promise.all([
|
|
promise_rejects(t, error2, writePromise,
|
|
'writePromise 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 settle');
|
|
|
|
const writePromise3 = writer.write('a');
|
|
|
|
return Promise.all([
|
|
promise_rejects(t, new TypeError(), writePromise3,
|
|
'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')
|
|
]);
|
|
}).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')
|
|
]);
|
|
});
|
|
}, 'writer.abort() while there is an in-flight write, and then finish the write with rejection');
|
|
|
|
promise_test(t => {
|
|
let resolveWrite;
|
|
let controller;
|
|
const ws = new WritableStream({
|
|
write(chunk, c) {
|
|
controller = c;
|
|
return new Promise(resolve => {
|
|
resolveWrite = resolve;
|
|
});
|
|
}
|
|
});
|
|
|
|
let writePromise;
|
|
let abortPromise;
|
|
|
|
const events = [];
|
|
|
|
const writer = ws.getWriter();
|
|
|
|
writer.closed.catch(() => {
|
|
events.push('closed');
|
|
});
|
|
|
|
// Wait for ws to start
|
|
return flushAsyncEvents().then(() => {
|
|
writePromise = writer.write('a');
|
|
writePromise.then(() => {
|
|
events.push('writePromise');
|
|
});
|
|
|
|
abortPromise = writer.abort(error1);
|
|
abortPromise.then(() => {
|
|
events.push('abortPromise');
|
|
});
|
|
|
|
const writePromise2 = writer.write('a');
|
|
|
|
return Promise.all([
|
|
promise_rejects(t, new TypeError(), writePromise2, 'writePromise2 must reject with an error indicating abort'),
|
|
promise_rejects(t, new TypeError(), writer.ready, 'writer.ready must reject with an error indicating abort'),
|
|
flushAsyncEvents()
|
|
]);
|
|
}).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 abort'),
|
|
promise_rejects(t, new TypeError(), writer.ready,
|
|
'writer.ready must be still rejected with the error indicating abort'),
|
|
flushAsyncEvents()
|
|
]);
|
|
}).then(() => {
|
|
assert_array_equals(
|
|
events, [],
|
|
'writePromise, abortPromise and writer.closed must not be fulfilled/rejected yet even after ' +
|
|
'controller.error() call');
|
|
|
|
resolveWrite();
|
|
|
|
return Promise.all([
|
|
writePromise,
|
|
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 settle');
|
|
|
|
const writePromise4 = writer.write('a');
|
|
|
|
return Promise.all([
|
|
writePromise,
|
|
promise_rejects(t, new TypeError(), writePromise4,
|
|
'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')
|
|
]);
|
|
}).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')
|
|
]);
|
|
});
|
|
}, '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 = recordingWritableStream({
|
|
write(chunk, c) {
|
|
controller = c;
|
|
return new Promise(resolve => {
|
|
resolveWrite = resolve;
|
|
});
|
|
}
|
|
});
|
|
|
|
let writePromise;
|
|
let abortPromise;
|
|
|
|
const events = [];
|
|
|
|
const writer = ws.getWriter();
|
|
|
|
writer.closed.catch(() => {
|
|
events.push('closed');
|
|
});
|
|
|
|
// Wait for ws to start
|
|
return flushAsyncEvents().then(() => {
|
|
writePromise = writer.write('a');
|
|
writePromise.then(() => {
|
|
events.push('writePromise');
|
|
});
|
|
|
|
controller.error(error2);
|
|
|
|
const writePromise2 = writer.write('a');
|
|
|
|
return Promise.all([
|
|
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()
|
|
]);
|
|
}).then(() => {
|
|
assert_array_equals(events, [], 'writePromise and writer.closed must not be fulfilled/rejected yet');
|
|
|
|
abortPromise = writer.abort(error1);
|
|
abortPromise.catch(() => {
|
|
events.push('abortPromise');
|
|
});
|
|
|
|
const writePromise3 = writer.write('a');
|
|
|
|
return Promise.all([
|
|
promise_rejects(t, error2, writePromise3,
|
|
'writePromise3 must reject with the error passed to the controller\'s error method'),
|
|
flushAsyncEvents()
|
|
]);
|
|
}).then(() => {
|
|
assert_array_equals(
|
|
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, ['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, 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')
|
|
]);
|
|
}).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 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;
|
|
const ws = new WritableStream({
|
|
write() {
|
|
return new Promise(resolve => {
|
|
resolveWrite = resolve;
|
|
});
|
|
}
|
|
});
|
|
const writer = ws.getWriter();
|
|
return writer.ready.then(() => {
|
|
const writePromise = writer.write('a');
|
|
const closed = writer.closed;
|
|
const abortPromise = writer.abort();
|
|
writer.releaseLock();
|
|
resolveWrite();
|
|
return Promise.all([
|
|
writePromise,
|
|
abortPromise,
|
|
promise_rejects(t, new TypeError(), closed, 'closed should reject')]);
|
|
});
|
|
}, '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;
|
|
let resolveAbortStarted;
|
|
const abortStarted = new Promise(resolve => {
|
|
resolveAbortStarted = resolve;
|
|
});
|
|
const ws = new WritableStream({
|
|
write() {
|
|
return new Promise(resolve => {
|
|
resolveWrite = resolve;
|
|
});
|
|
},
|
|
abort() {
|
|
resolveAbortStarted();
|
|
return new Promise(resolve => {
|
|
resolveAbort = resolve;
|
|
});
|
|
}
|
|
});
|
|
const writer = ws.getWriter();
|
|
return writer.ready.then(() => {
|
|
const writePromise = writer.write('a');
|
|
const closed = writer.closed;
|
|
const abortPromise = writer.abort();
|
|
resolveWrite();
|
|
return abortStarted.then(() => {
|
|
writer.releaseLock();
|
|
assert_equals(writer.closed, closed, 'closed promise should not have changed');
|
|
resolveAbort();
|
|
return Promise.all([
|
|
writePromise,
|
|
abortPromise,
|
|
promise_rejects(t, new TypeError(), closed, 'closed should reject')]);
|
|
});
|
|
});
|
|
}, '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();
|