331 lines
9.9 KiB
JavaScript
331 lines
9.9 KiB
JavaScript
// META: global=window,worker,shadowrealm
|
|
// META: script=../resources/rs-utils.js
|
|
// META: script=../resources/test-utils.js
|
|
// META: script=../resources/recording-streams.js
|
|
'use strict';
|
|
|
|
function duckTypedPassThroughTransform() {
|
|
let enqueueInReadable;
|
|
let closeReadable;
|
|
|
|
return {
|
|
writable: new WritableStream({
|
|
write(chunk) {
|
|
enqueueInReadable(chunk);
|
|
},
|
|
|
|
close() {
|
|
closeReadable();
|
|
}
|
|
}),
|
|
|
|
readable: new ReadableStream({
|
|
start(c) {
|
|
enqueueInReadable = c.enqueue.bind(c);
|
|
closeReadable = c.close.bind(c);
|
|
}
|
|
})
|
|
};
|
|
}
|
|
|
|
function uninterestingReadableWritablePair() {
|
|
return { writable: new WritableStream(), readable: new ReadableStream() };
|
|
}
|
|
|
|
promise_test(() => {
|
|
const readableEnd = sequentialReadableStream(5).pipeThrough(duckTypedPassThroughTransform());
|
|
|
|
return readableStreamToArray(readableEnd).then(chunks =>
|
|
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(() => {
|
|
const transform = {
|
|
writable: new WritableStream({
|
|
start(c) {
|
|
c.error(new Error('this rejection should not be reported as unhandled'));
|
|
}
|
|
}),
|
|
readable: new ReadableStream()
|
|
};
|
|
|
|
sequentialReadableStream(5).pipeThrough(transform);
|
|
|
|
// The test harness should complain about unhandled rejections by then.
|
|
return flushAsyncEvents();
|
|
|
|
}, 'Piping through a transform errored on the writable end does not cause an unhandled promise rejection');
|
|
|
|
test(() => {
|
|
let calledPipeTo = false;
|
|
class BadReadableStream extends ReadableStream {
|
|
pipeTo() {
|
|
calledPipeTo = true;
|
|
}
|
|
}
|
|
|
|
const brs = new BadReadableStream({
|
|
start(controller) {
|
|
controller.close();
|
|
}
|
|
});
|
|
const readable = new ReadableStream();
|
|
const writable = new WritableStream();
|
|
const result = brs.pipeThrough({ readable, writable });
|
|
|
|
assert_false(calledPipeTo, 'the overridden pipeTo should not have been called');
|
|
assert_equals(result, readable, 'return value should be the passed readable property');
|
|
}, 'pipeThrough should not call pipeTo on this');
|
|
|
|
test(t => {
|
|
let calledFakePipeTo = false;
|
|
const realPipeTo = ReadableStream.prototype.pipeTo;
|
|
t.add_cleanup(() => {
|
|
ReadableStream.prototype.pipeTo = realPipeTo;
|
|
});
|
|
ReadableStream.prototype.pipeTo = () => {
|
|
calledFakePipeTo = true;
|
|
};
|
|
const rs = new ReadableStream();
|
|
const readable = new ReadableStream();
|
|
const writable = new WritableStream();
|
|
const result = rs.pipeThrough({ readable, writable });
|
|
|
|
assert_false(calledFakePipeTo, 'the monkey-patched pipeTo should not have been called');
|
|
assert_equals(result, readable, 'return value should be the passed readable property');
|
|
|
|
}, 'pipeThrough should not call pipeTo on the ReadableStream prototype');
|
|
|
|
const badReadables = [null, undefined, 0, NaN, true, 'ReadableStream', Object.create(ReadableStream.prototype)];
|
|
for (const readable of badReadables) {
|
|
test(() => {
|
|
assert_throws_js(TypeError,
|
|
ReadableStream.prototype.pipeThrough.bind(readable, uninterestingReadableWritablePair()),
|
|
'pipeThrough should throw');
|
|
}, `pipeThrough should brand-check this and not allow '${readable}'`);
|
|
|
|
test(() => {
|
|
const rs = new ReadableStream();
|
|
let writableGetterCalled = false;
|
|
assert_throws_js(
|
|
TypeError,
|
|
() => rs.pipeThrough({
|
|
get writable() {
|
|
writableGetterCalled = true;
|
|
return new WritableStream();
|
|
},
|
|
readable
|
|
}),
|
|
'pipeThrough should brand-check readable'
|
|
);
|
|
assert_false(writableGetterCalled, 'writable should not have been accessed');
|
|
}, `pipeThrough should brand-check readable and not allow '${readable}'`);
|
|
}
|
|
|
|
const badWritables = [null, undefined, 0, NaN, true, 'WritableStream', Object.create(WritableStream.prototype)];
|
|
for (const writable of badWritables) {
|
|
test(() => {
|
|
const rs = new ReadableStream({
|
|
start(c) {
|
|
c.close();
|
|
}
|
|
});
|
|
let readableGetterCalled = false;
|
|
assert_throws_js(TypeError, () => rs.pipeThrough({
|
|
get readable() {
|
|
readableGetterCalled = true;
|
|
return new ReadableStream();
|
|
},
|
|
writable
|
|
}),
|
|
'pipeThrough should brand-check writable');
|
|
assert_true(readableGetterCalled, 'readable should have been accessed');
|
|
}, `pipeThrough should brand-check writable and not allow '${writable}'`);
|
|
}
|
|
|
|
test(t => {
|
|
const error = new Error();
|
|
error.name = 'custom';
|
|
|
|
const rs = new ReadableStream({
|
|
pull: t.unreached_func('pull should not be called')
|
|
}, { highWaterMark: 0 });
|
|
|
|
const throwingWritable = {
|
|
readable: rs,
|
|
get writable() {
|
|
throw error;
|
|
}
|
|
};
|
|
assert_throws_exactly(error,
|
|
() => ReadableStream.prototype.pipeThrough.call(rs, throwingWritable, {}),
|
|
'pipeThrough should rethrow the error thrown by the writable getter');
|
|
|
|
const throwingReadable = {
|
|
get readable() {
|
|
throw error;
|
|
},
|
|
writable: {}
|
|
};
|
|
assert_throws_exactly(error,
|
|
() => ReadableStream.prototype.pipeThrough.call(rs, throwingReadable, {}),
|
|
'pipeThrough should rethrow the error thrown by the readable getter');
|
|
|
|
}, 'pipeThrough should rethrow errors from accessing readable or writable');
|
|
|
|
const badSignals = [null, 0, NaN, true, 'AbortSignal', Object.create(AbortSignal.prototype)];
|
|
for (const signal of badSignals) {
|
|
test(() => {
|
|
const rs = new ReadableStream();
|
|
assert_throws_js(TypeError, () => rs.pipeThrough(uninterestingReadableWritablePair(), { signal }),
|
|
'pipeThrough should throw');
|
|
}, `invalid values of signal should throw; specifically '${signal}'`);
|
|
}
|
|
|
|
test(() => {
|
|
const rs = new ReadableStream();
|
|
const controller = new AbortController();
|
|
const signal = controller.signal;
|
|
rs.pipeThrough(uninterestingReadableWritablePair(), { signal });
|
|
}, 'pipeThrough should accept a real AbortSignal');
|
|
|
|
test(() => {
|
|
const rs = new ReadableStream();
|
|
rs.getReader();
|
|
assert_throws_js(TypeError, () => rs.pipeThrough(uninterestingReadableWritablePair()),
|
|
'pipeThrough should throw');
|
|
}, 'pipeThrough should throw if this is locked');
|
|
|
|
test(() => {
|
|
const rs = new ReadableStream();
|
|
const writable = new WritableStream();
|
|
const readable = new ReadableStream();
|
|
writable.getWriter();
|
|
assert_throws_js(TypeError, () => rs.pipeThrough({writable, readable}),
|
|
'pipeThrough should throw');
|
|
}, 'pipeThrough should throw if writable is locked');
|
|
|
|
test(() => {
|
|
const rs = new ReadableStream();
|
|
const writable = new WritableStream();
|
|
const readable = new ReadableStream();
|
|
readable.getReader();
|
|
assert_equals(rs.pipeThrough({ writable, readable }), readable,
|
|
'pipeThrough should not throw');
|
|
}, 'pipeThrough should not care if readable is locked');
|
|
|
|
promise_test(() => {
|
|
const rs = recordingReadableStream();
|
|
const writable = new WritableStream({
|
|
start(controller) {
|
|
controller.error();
|
|
}
|
|
});
|
|
const readable = new ReadableStream();
|
|
rs.pipeThrough({ writable, readable }, { preventCancel: true });
|
|
return flushAsyncEvents(0).then(() => {
|
|
assert_array_equals(rs.events, ['pull'], 'cancel should not have been called');
|
|
});
|
|
}, 'preventCancel should work');
|
|
|
|
promise_test(() => {
|
|
const rs = new ReadableStream({
|
|
start(controller) {
|
|
controller.close();
|
|
}
|
|
});
|
|
const writable = recordingWritableStream();
|
|
const readable = new ReadableStream();
|
|
rs.pipeThrough({ writable, readable }, { preventClose: true });
|
|
return flushAsyncEvents(0).then(() => {
|
|
assert_array_equals(writable.events, [], 'writable should not be closed');
|
|
});
|
|
}, 'preventClose should work');
|
|
|
|
promise_test(() => {
|
|
const rs = new ReadableStream({
|
|
start(controller) {
|
|
controller.error();
|
|
}
|
|
});
|
|
const writable = recordingWritableStream();
|
|
const readable = new ReadableStream();
|
|
rs.pipeThrough({ writable, readable }, { preventAbort: true });
|
|
return flushAsyncEvents(0).then(() => {
|
|
assert_array_equals(writable.events, [], 'writable should not be aborted');
|
|
});
|
|
}, 'preventAbort should work');
|
|
|
|
test(() => {
|
|
const rs = new ReadableStream();
|
|
const readable = new ReadableStream();
|
|
const writable = new WritableStream();
|
|
assert_throws_js(TypeError, () => rs.pipeThrough({readable, writable}, {
|
|
get preventAbort() {
|
|
writable.getWriter();
|
|
}
|
|
}), 'pipeThrough should throw');
|
|
}, 'pipeThrough() should throw if an option getter grabs a writer');
|
|
|
|
test(() => {
|
|
const rs = new ReadableStream();
|
|
const readable = new ReadableStream();
|
|
const writable = new WritableStream();
|
|
rs.pipeThrough({readable, writable}, null);
|
|
}, 'pipeThrough() should not throw if option is null');
|
|
|
|
test(() => {
|
|
const rs = new ReadableStream();
|
|
const readable = new ReadableStream();
|
|
const writable = new WritableStream();
|
|
rs.pipeThrough({readable, writable}, {signal:undefined});
|
|
}, 'pipeThrough() should not throw if signal is undefined');
|
|
|
|
function tryPipeThrough(pair, options)
|
|
{
|
|
const rs = new ReadableStream();
|
|
if (!pair)
|
|
pair = {readable:new ReadableStream(), writable:new WritableStream()};
|
|
try {
|
|
rs.pipeThrough(pair, options)
|
|
} catch (e) {
|
|
return e;
|
|
}
|
|
}
|
|
|
|
test(() => {
|
|
let result = tryPipeThrough({
|
|
get readable() {
|
|
return new ReadableStream();
|
|
},
|
|
get writable() {
|
|
throw "writable threw";
|
|
}
|
|
}, { });
|
|
assert_equals(result, "writable threw");
|
|
|
|
result = tryPipeThrough({
|
|
get readable() {
|
|
throw "readable threw";
|
|
},
|
|
get writable() {
|
|
throw "writable threw";
|
|
}
|
|
}, { });
|
|
assert_equals(result, "readable threw");
|
|
|
|
result = tryPipeThrough({
|
|
get readable() {
|
|
throw "readable threw";
|
|
},
|
|
get writable() {
|
|
throw "writable threw";
|
|
}
|
|
}, {
|
|
get preventAbort() {
|
|
throw "preventAbort threw";
|
|
}
|
|
});
|
|
assert_equals(result, "readable threw");
|
|
|
|
}, 'pipeThrough() should throw if readable/writable getters throw');
|