Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

// 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');