gecko-dev/testing/web-platform/tests/streams/transform-streams/general.any.js
Jason Orendorff 47519c94b2 Bug 1513570 - Part 1: Adapt stream tests to run in the shell. r=Ms2ger
We don't support byte streams, transform streams, writable streams, or piping
yet, but we will, and in the meantime our own meta files disable those tests
for us.

Differential Revision: https://phabricator.services.mozilla.com/D14314

--HG--
extra : moz-landing-system : lando
2018-12-14 18:14:56 +00:00

463 lines
15 KiB
JavaScript

// META: global=worker,jsshell
// META: script=../resources/test-utils.js
// META: script=../resources/rs-utils.js
'use strict';
test(() => {
new TransformStream({ transform() { } });
}, 'TransformStream can be constructed with a transform function');
test(() => {
new TransformStream();
new TransformStream({});
}, 'TransformStream can be constructed with no transform function');
test(() => {
const ts = new TransformStream({ transform() { } });
const proto = Object.getPrototypeOf(ts);
const writableStream = Object.getOwnPropertyDescriptor(proto, 'writable');
assert_true(writableStream !== undefined, 'it has a writable property');
assert_false(writableStream.enumerable, 'writable should be non-enumerable');
assert_equals(typeof writableStream.get, 'function', 'writable should have a getter');
assert_equals(writableStream.set, undefined, 'writable should not have a setter');
assert_true(writableStream.configurable, 'writable should be configurable');
assert_true(ts.writable instanceof WritableStream, 'writable is an instance of WritableStream');
assert_not_equals(WritableStream.prototype.getWriter.call(ts.writable), undefined,
'writable should pass WritableStream brand check');
const readableStream = Object.getOwnPropertyDescriptor(proto, 'readable');
assert_true(readableStream !== undefined, 'it has a readable property');
assert_false(readableStream.enumerable, 'readable should be non-enumerable');
assert_equals(typeof readableStream.get, 'function', 'readable should have a getter');
assert_equals(readableStream.set, undefined, 'readable should not have a setter');
assert_true(readableStream.configurable, 'readable should be configurable');
assert_true(ts.readable instanceof ReadableStream, 'readable is an instance of ReadableStream');
assert_not_equals(ReadableStream.prototype.getReader.call(ts.readable), undefined,
'readable should pass ReadableStream brand check');
}, 'TransformStream instances must have writable and readable properties of the correct types');
test(() => {
const ts = new TransformStream({ transform() { } });
const writer = ts.writable.getWriter();
assert_equals(writer.desiredSize, 1, 'writer.desiredSize should be 1');
}, 'TransformStream writable starts in the writable state');
promise_test(() => {
const ts = new TransformStream();
const writer = ts.writable.getWriter();
writer.write('a');
assert_equals(writer.desiredSize, 0, 'writer.desiredSize should be 0 after write()');
return ts.readable.getReader().read().then(result => {
assert_equals(result.value, 'a',
'result from reading the readable is the same as was written to writable');
assert_false(result.done, 'stream should not be done');
return delay(0).then(() => assert_equals(writer.desiredSize, 1, 'desiredSize should be 1 again'));
});
}, 'Identity TransformStream: can read from readable what is put into writable');
promise_test(() => {
let c;
const ts = new TransformStream({
start(controller) {
c = controller;
},
transform(chunk) {
c.enqueue(chunk.toUpperCase());
}
});
const writer = ts.writable.getWriter();
writer.write('a');
return ts.readable.getReader().read().then(result => {
assert_equals(result.value, 'A',
'result from reading the readable is the transformation of what was written to writable');
assert_false(result.done, 'stream should not be done');
});
}, 'Uppercaser sync TransformStream: can read from readable transformed version of what is put into writable');
promise_test(() => {
let c;
const ts = new TransformStream({
start(controller) {
c = controller;
},
transform(chunk) {
c.enqueue(chunk.toUpperCase());
c.enqueue(chunk.toUpperCase());
}
});
const writer = ts.writable.getWriter();
writer.write('a');
const reader = ts.readable.getReader();
return reader.read().then(result1 => {
assert_equals(result1.value, 'A',
'the first chunk read is the transformation of the single chunk written');
assert_false(result1.done, 'stream should not be done');
return reader.read().then(result2 => {
assert_equals(result2.value, 'A',
'the second chunk read is also the transformation of the single chunk written');
assert_false(result2.done, 'stream should not be done');
});
});
}, 'Uppercaser-doubler sync TransformStream: can read both chunks put into the readable');
promise_test(() => {
let c;
const ts = new TransformStream({
start(controller) {
c = controller;
},
transform(chunk) {
return delay(0).then(() => c.enqueue(chunk.toUpperCase()));
}
});
const writer = ts.writable.getWriter();
writer.write('a');
return ts.readable.getReader().read().then(result => {
assert_equals(result.value, 'A',
'result from reading the readable is the transformation of what was written to writable');
assert_false(result.done, 'stream should not be done');
});
}, 'Uppercaser async TransformStream: can read from readable transformed version of what is put into writable');
promise_test(() => {
let doSecondEnqueue;
let returnFromTransform;
const ts = new TransformStream({
transform(chunk, controller) {
delay(0).then(() => controller.enqueue(chunk.toUpperCase()));
doSecondEnqueue = () => controller.enqueue(chunk.toUpperCase());
return new Promise(resolve => {
returnFromTransform = resolve;
});
}
});
const reader = ts.readable.getReader();
const writer = ts.writable.getWriter();
writer.write('a');
return reader.read().then(result1 => {
assert_equals(result1.value, 'A',
'the first chunk read is the transformation of the single chunk written');
assert_false(result1.done, 'stream should not be done');
doSecondEnqueue();
return reader.read().then(result2 => {
assert_equals(result2.value, 'A',
'the second chunk read is also the transformation of the single chunk written');
assert_false(result2.done, 'stream should not be done');
returnFromTransform();
});
});
}, 'Uppercaser-doubler async TransformStream: can read both chunks put into the readable');
promise_test(() => {
const ts = new TransformStream({ transform() { } });
const writer = ts.writable.getWriter();
writer.close();
return Promise.all([writer.closed, ts.readable.getReader().closed]);
}, 'TransformStream: by default, closing the writable closes the readable (when there are no queued writes)');
promise_test(() => {
let transformResolve;
const transformPromise = new Promise(resolve => {
transformResolve = resolve;
});
const ts = new TransformStream({
transform() {
return transformPromise;
}
}, undefined, { highWaterMark: 1 });
const writer = ts.writable.getWriter();
writer.write('a');
writer.close();
let rsClosed = false;
ts.readable.getReader().closed.then(() => {
rsClosed = true;
});
return delay(0).then(() => {
assert_equals(rsClosed, false, 'readable is not closed after a tick');
transformResolve();
return writer.closed.then(() => {
// TODO: Is this expectation correct?
assert_equals(rsClosed, true, 'readable is closed at that point');
});
});
}, 'TransformStream: by default, closing the writable waits for transforms to finish before closing both');
promise_test(() => {
let c;
const ts = new TransformStream({
start(controller) {
c = controller;
},
transform() {
c.enqueue('x');
c.enqueue('y');
return delay(0);
}
});
const writer = ts.writable.getWriter();
writer.write('a');
writer.close();
const readableChunks = readableStreamToArray(ts.readable);
return writer.closed.then(() => {
return readableChunks.then(chunks => {
assert_array_equals(chunks, ['x', 'y'], 'both enqueued chunks can be read from the readable');
});
});
}, 'TransformStream: by default, closing the writable closes the readable after sync enqueues and async done');
promise_test(() => {
let c;
const ts = new TransformStream({
start(controller) {
c = controller;
},
transform() {
return delay(0)
.then(() => c.enqueue('x'))
.then(() => c.enqueue('y'))
.then(() => delay(0));
}
});
const writer = ts.writable.getWriter();
writer.write('a');
writer.close();
const readableChunks = readableStreamToArray(ts.readable);
return writer.closed.then(() => {
return readableChunks.then(chunks => {
assert_array_equals(chunks, ['x', 'y'], 'both enqueued chunks can be read from the readable');
});
});
}, 'TransformStream: by default, closing the writable closes the readable after async enqueues and async done');
promise_test(() => {
let c;
const ts = new TransformStream({
suffix: '-suffix',
start(controller) {
c = controller;
c.enqueue('start' + this.suffix);
},
transform(chunk) {
c.enqueue(chunk + this.suffix);
},
flush() {
c.enqueue('flushed' + this.suffix);
}
});
const writer = ts.writable.getWriter();
writer.write('a');
writer.close();
const readableChunks = readableStreamToArray(ts.readable);
return writer.closed.then(() => {
return readableChunks.then(chunks => {
assert_array_equals(chunks, ['start-suffix', 'a-suffix', 'flushed-suffix'], 'all enqueued chunks have suffixes');
});
});
}, 'Transform stream should call transformer methods as methods');
promise_test(() => {
function functionWithOverloads() {}
functionWithOverloads.apply = () => assert_unreached('apply() should not be called');
functionWithOverloads.call = () => assert_unreached('call() should not be called');
const ts = new TransformStream({
start: functionWithOverloads,
transform: functionWithOverloads,
flush: functionWithOverloads
});
const writer = ts.writable.getWriter();
writer.write('a');
writer.close();
return readableStreamToArray(ts.readable);
}, 'methods should not not have .apply() or .call() called');
promise_test(t => {
let startCalled = false;
let startDone = false;
let transformDone = false;
let flushDone = false;
const ts = new TransformStream({
start() {
startCalled = true;
return flushAsyncEvents().then(() => {
startDone = true;
});
},
transform() {
return t.step(() => {
assert_true(startDone, 'transform() should not be called until the promise returned from start() has resolved');
return flushAsyncEvents().then(() => {
transformDone = true;
});
});
},
flush() {
return t.step(() => {
assert_true(transformDone,
'flush() should not be called until the promise returned from transform() has resolved');
return flushAsyncEvents().then(() => {
flushDone = true;
});
});
}
}, undefined, { highWaterMark: 1 });
assert_true(startCalled, 'start() should be called synchronously');
const writer = ts.writable.getWriter();
const writePromise = writer.write('a');
return writer.close().then(() => {
assert_true(flushDone, 'promise returned from flush() should have resolved');
return writePromise;
});
}, 'TransformStream start, transform, and flush should be strictly ordered');
promise_test(() => {
let transformCalled = false;
const ts = new TransformStream({
transform() {
transformCalled = true;
}
}, undefined, { highWaterMark: Infinity });
// transform() is only called synchronously when there is no backpressure and all microtasks have run.
return delay(0).then(() => {
const writePromise = ts.writable.getWriter().write();
assert_true(transformCalled, 'transform() should have been called');
return writePromise;
});
}, 'it should be possible to call transform() synchronously');
promise_test(() => {
const ts = new TransformStream({}, undefined, { highWaterMark: 0 });
const writer = ts.writable.getWriter();
writer.close();
return Promise.all([writer.closed, ts.readable.getReader().closed]);
}, 'closing the writable should close the readable when there are no queued chunks, even with backpressure');
test(() => {
new TransformStream({
start(controller) {
controller.terminate();
assert_throws(new TypeError(), () => controller.enqueue(), 'enqueue should throw');
}
});
}, 'enqueue() should throw after controller.terminate()');
promise_test(() => {
let controller;
const ts = new TransformStream({
start(c) {
controller = c;
}
});
const cancelPromise = ts.readable.cancel();
assert_throws(new TypeError(), () => controller.enqueue(), 'enqueue should throw');
return cancelPromise;
}, 'enqueue() should throw after readable.cancel()');
test(() => {
new TransformStream({
start(controller) {
controller.terminate();
controller.terminate();
}
});
}, 'controller.terminate() should do nothing the second time it is called');
promise_test(t => {
let controller;
const ts = new TransformStream({
start(c) {
controller = c;
}
});
const cancelReason = { name: 'cancelReason' };
const cancelPromise = ts.readable.cancel(cancelReason);
controller.terminate();
return Promise.all([
cancelPromise,
promise_rejects(t, cancelReason, ts.writable.getWriter().closed, 'closed should reject with cancelReason')
]);
}, 'terminate() should do nothing after readable.cancel()');
promise_test(() => {
let calls = 0;
new TransformStream({
start() {
++calls;
}
});
return flushAsyncEvents().then(() => {
assert_equals(calls, 1, 'start() should have been called exactly once');
});
}, 'start() should not be called twice');
test(() => {
assert_throws(new RangeError(), () => new TransformStream({ readableType: 'bytes' }), 'constructor should throw');
}, 'specifying a defined readableType should throw');
test(() => {
assert_throws(new RangeError(), () => new TransformStream({ writableType: 'bytes' }), 'constructor should throw');
}, 'specifying a defined writableType should throw');
test(() => {
class Subclass extends TransformStream {
extraFunction() {
return true;
}
}
assert_equals(
Object.getPrototypeOf(Subclass.prototype), TransformStream.prototype,
'Subclass.prototype\'s prototype should be TransformStream.prototype');
assert_equals(Object.getPrototypeOf(Subclass), TransformStream,
'Subclass\'s prototype should be TransformStream');
const sub = new Subclass();
assert_true(sub instanceof TransformStream,
'Subclass object should be an instance of TransformStream');
assert_true(sub instanceof Subclass,
'Subclass object should be an instance of Subclass');
const readableGetter = Object.getOwnPropertyDescriptor(
TransformStream.prototype, 'readable').get;
assert_equals(readableGetter.call(sub), sub.readable,
'Subclass object should pass brand check');
assert_true(sub.extraFunction(),
'extraFunction() should be present on Subclass object');
}, 'Subclassing TransformStream should work');