Skip to content

Commit

Permalink
Streams: tests for Transformer.cancel (#40453)
Browse files Browse the repository at this point in the history
Co-authored-by: Mattias Buelens <mattias@buelens.com>
Co-authored-by: Domenic Denicola <d@domenic.me>
  • Loading branch information
3 people authored Sep 29, 2023
1 parent 3154c3f commit a8872d9
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 14 deletions.
115 changes: 115 additions & 0 deletions streams/transform-streams/cancel.any.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// META: global=window,worker,shadowrealm
// META: script=../resources/test-utils.js
'use strict';

const thrownError = new Error('bad things are happening!');
thrownError.name = 'error1';

const originalReason = new Error('original reason');
originalReason.name = 'error2';

promise_test(async t => {
let cancelled = undefined;
const ts = new TransformStream({
cancel(reason) {
cancelled = reason;
}
});
const res = await ts.readable.cancel(thrownError);
assert_equals(res, undefined, 'readable.cancel() should return undefined');
assert_equals(cancelled, thrownError, 'transformer.cancel() should be called with the passed reason');
}, 'cancelling the readable side should call transformer.cancel()');

promise_test(async t => {
const ts = new TransformStream({
cancel(reason) {
assert_equals(reason, originalReason, 'transformer.cancel() should be called with the passed reason');
throw thrownError;
}
});
const writer = ts.writable.getWriter();
const cancelPromise = ts.readable.cancel(originalReason);
await promise_rejects_exactly(t, thrownError, cancelPromise, 'readable.cancel() should reject with thrownError');
await promise_rejects_exactly(t, thrownError, writer.closed, 'writer.closed should reject with thrownError');
}, 'cancelling the readable side should reject if transformer.cancel() throws');

promise_test(async t => {
let aborted = undefined;
const ts = new TransformStream({
cancel(reason) {
aborted = reason;
},
flush: t.unreached_func('flush should not be called')
});
const res = await ts.writable.abort(thrownError);
assert_equals(res, undefined, 'writable.abort() should return undefined');
assert_equals(aborted, thrownError, 'transformer.abort() should be called with the passed reason');
}, 'aborting the writable side should call transformer.abort()');

promise_test(async t => {
const ts = new TransformStream({
cancel(reason) {
assert_equals(reason, originalReason, 'transformer.cancel() should be called with the passed reason');
throw thrownError;
},
flush: t.unreached_func('flush should not be called')
});
const reader = ts.readable.getReader();
const abortPromise = ts.writable.abort(originalReason);
await promise_rejects_exactly(t, thrownError, abortPromise, 'writable.abort() should reject with thrownError');
await promise_rejects_exactly(t, thrownError, reader.closed, 'reader.closed should reject with thrownError');
}, 'aborting the writable side should reject if transformer.cancel() throws');

promise_test(async t => {
const ts = new TransformStream({
async cancel(reason) {
assert_equals(reason, originalReason, 'transformer.cancel() should be called with the passed reason');
throw thrownError;
},
flush: t.unreached_func('flush should not be called')
});
const cancelPromise = ts.readable.cancel(originalReason);
const closePromise = ts.writable.close();
await Promise.all([
promise_rejects_exactly(t, thrownError, cancelPromise, 'cancelPromise should reject with thrownError'),
promise_rejects_exactly(t, thrownError, closePromise, 'closePromise should reject with thrownError'),
]);
}, 'closing the writable side should reject if a parallel transformer.cancel() throws');

promise_test(async t => {
let controller;
const ts = new TransformStream({
start(c) {
controller = c;
},
async cancel(reason) {
assert_equals(reason, originalReason, 'transformer.cancel() should be called with the passed reason');
controller.error(thrownError);
},
flush: t.unreached_func('flush should not be called')
});
const cancelPromise = ts.readable.cancel(originalReason);
const closePromise = ts.writable.close();
await Promise.all([
promise_rejects_exactly(t, thrownError, cancelPromise, 'cancelPromise should reject with thrownError'),
promise_rejects_exactly(t, thrownError, closePromise, 'closePromise should reject with thrownError'),
]);
}, 'readable.cancel() and a parallel writable.close() should reject if a transformer.cancel() calls controller.error()');

promise_test(async t => {
let controller;
const ts = new TransformStream({
start(c) {
controller = c;
},
async cancel(reason) {
assert_equals(reason, originalReason, 'transformer.cancel() should be called with the passed reason');
controller.error(thrownError);
},
flush: t.unreached_func('flush should not be called')
});
const cancelPromise = ts.writable.abort(originalReason);
await promise_rejects_exactly(t, thrownError, cancelPromise, 'cancelPromise should reject with thrownError');
const closePromise = ts.readable.cancel(1);
await promise_rejects_exactly(t, thrownError, closePromise, 'closePromise should reject with thrownError');
}, 'writable.abort() and readable.cancel() should reject if a transformer.cancel() calls controller.error()');
43 changes: 31 additions & 12 deletions streams/transform-streams/errors.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ promise_test(t => {
const ts = new TransformStream({
transform() {
throw thrownError;
}
},
cancel: t.unreached_func('cancel should not be called')
});

const reader = ts.readable.getReader();
Expand All @@ -34,7 +35,8 @@ promise_test(t => {
},
flush() {
throw thrownError;
}
},
cancel: t.unreached_func('cancel should not be called')
});

const reader = ts.readable.getReader();
Expand All @@ -54,13 +56,14 @@ promise_test(t => {
]);
}, 'TransformStream errors thrown in flush put the writable and readable in an errored state');

test(() => {
test(t => {
new TransformStream({
start(c) {
c.enqueue('a');
c.error(new Error('generic error'));
assert_throws_js(TypeError, () => c.enqueue('b'), 'enqueue() should throw');
}
},
cancel: t.unreached_func('cancel should not be called')
});
}, 'errored TransformStream should not enqueue new chunks');

Expand All @@ -72,7 +75,8 @@ promise_test(t => {
});
},
transform: t.unreached_func('transform should not be called'),
flush: t.unreached_func('flush should not be called')
flush: t.unreached_func('flush should not be called'),
cancel: t.unreached_func('cancel should not be called')
});

const writer = ts.writable.getWriter();
Expand All @@ -96,7 +100,8 @@ promise_test(t => {
});
},
transform: t.unreached_func('transform should never be called if start() fails'),
flush: t.unreached_func('flush should never be called if start() fails')
flush: t.unreached_func('flush should never be called if start() fails'),
cancel: t.unreached_func('cancel should never be called if start() fails')
});

const writer = ts.writable.getWriter();
Expand Down Expand Up @@ -202,9 +207,10 @@ promise_test(t => {
return Promise.all([
abortPromise,
cancelPromise,
promise_rejects_exactly(t, thrownError, writer.closed, 'writer.closed should reject with thrownError')]);
}, 'abort should set the close reason for the writable when it happens before cancel during start, but cancel should ' +
'still succeed');
promise_rejects_exactly(t, thrownError, writer.closed, 'writer.closed should reject'),
]);
}, 'abort should set the close reason for the writable when it happens before cancel during start, and cancel should ' +
'reject');

promise_test(t => {
let resolveTransform;
Expand Down Expand Up @@ -251,13 +257,26 @@ promise_test(t => {
controller = c;
}
});
const cancelPromise = ts.readable.cancel(thrownError);
controller.error(ignoredError);
const cancelPromise = ts.readable.cancel(ignoredError);
controller.error(thrownError);
return Promise.all([
cancelPromise,
promise_rejects_exactly(t, thrownError, ts.writable.getWriter().closed, 'closed should reject with thrownError')
]);
}, 'controller.error() should do nothing after readable.cancel()');
}, 'controller.error() should close writable immediately after readable.cancel()');

promise_test(t => {
let controller;
const ts = new TransformStream({
start(c) {
controller = c;
}
});
return ts.readable.cancel(thrownError).then(() => {
controller.error(ignoredError);
return promise_rejects_exactly(t, thrownError, ts.writable.getWriter().closed, 'closed should reject with thrownError');
});
}, 'controller.error() should do nothing after readable.cancel() resolves');

promise_test(t => {
let controller;
Expand Down
15 changes: 15 additions & 0 deletions streams/transform-streams/flush.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,18 @@ promise_test(t => {
});
return promise_rejects_exactly(t, error1, ts.writable.getWriter().close(), 'close() should reject');
}, 'error() during flush should cause writer.close() to reject');

promise_test(async t => {
let flushed = false;
const ts = new TransformStream({
flush() {
flushed = true;
},
cancel: t.unreached_func('cancel should not be called')
});
const closePromise = ts.writable.close();
await delay(0);
const cancelPromise = ts.readable.cancel(error1);
await Promise.all([closePromise, cancelPromise]);
assert_equals(flushed, true, 'transformer.flush() should be called');
}, 'closing the writable side should call transformer.flush() and a parallel readable.cancel() should not reject');
19 changes: 17 additions & 2 deletions streams/transform-streams/general.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,24 @@ promise_test(t => {
controller.terminate();
return Promise.all([
cancelPromise,
promise_rejects_exactly(t, cancelReason, ts.writable.getWriter().closed, 'closed should reject with cancelReason')
promise_rejects_js(t, TypeError, ts.writable.getWriter().closed, 'closed should reject with TypeError')
]);
}, 'terminate() should do nothing after readable.cancel()');
}, 'terminate() should abort writable immediately after readable.cancel()');

promise_test(t => {
let controller;
const ts = new TransformStream({
start(c) {
controller = c;
}
});
const cancelReason = { name: 'cancelReason' };
return ts.readable.cancel(cancelReason).then(() => {
controller.terminate();
return promise_rejects_exactly(t, cancelReason, ts.writable.getWriter().closed, 'closed should reject with TypeError');
})
}, 'terminate() should do nothing after readable.cancel() resolves');


promise_test(() => {
let calls = 0;
Expand Down
4 changes: 4 additions & 0 deletions streams/transform-streams/reentrant-strategies.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,10 @@ promise_test(t => {
// call to TransformStreamDefaultSink.
return delay(0).then(() => {
controller.enqueue('a');
return reader.read();
}).then(({ value, done }) => {
assert_false(done, 'done should be false');
assert_equals(value, 'a', 'value should be correct');
return Promise.all([promise_rejects_exactly(t, error1, reader.read(), 'read() should reject'), abortPromise]);
});
}, 'writer.abort() inside size() should work');

0 comments on commit a8872d9

Please sign in to comment.