summaryrefslogtreecommitdiff
path: root/cli/js/tests
diff options
context:
space:
mode:
Diffstat (limited to 'cli/js/tests')
-rw-r--r--cli/js/tests/streams_transform_test.ts562
-rw-r--r--cli/js/tests/unit_tests.ts1
2 files changed, 563 insertions, 0 deletions
diff --git a/cli/js/tests/streams_transform_test.ts b/cli/js/tests/streams_transform_test.ts
new file mode 100644
index 000000000..f3ec148ae
--- /dev/null
+++ b/cli/js/tests/streams_transform_test.ts
@@ -0,0 +1,562 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import {
+ unitTest,
+ assert,
+ assertEquals,
+ assertNotEquals,
+ assertThrows,
+} from "./test_util.ts";
+
+function delay(seconds: number): Promise<void> {
+ return new Promise<void>((resolve) => {
+ setTimeout(() => {
+ resolve();
+ }, seconds);
+ });
+}
+
+function readableStreamToArray<R>(
+ readable: { getReader(): ReadableStreamDefaultReader<R> },
+ reader?: ReadableStreamDefaultReader<R>
+): Promise<R[]> {
+ if (reader === undefined) {
+ reader = readable.getReader();
+ }
+
+ const chunks: R[] = [];
+
+ return pump();
+
+ function pump(): Promise<R[]> {
+ return reader!.read().then((result) => {
+ if (result.done) {
+ return chunks;
+ }
+
+ chunks.push(result.value);
+ return pump();
+ });
+ }
+}
+
+unitTest(function transformStreamConstructedWithTransformFunction() {
+ new TransformStream({ transform(): void {} });
+});
+
+unitTest(function transformStreamConstructedNoTransform() {
+ new TransformStream();
+ new TransformStream({});
+});
+
+unitTest(function transformStreamIntstancesHaveProperProperties() {
+ const ts = new TransformStream({ transform(): void {} });
+ const proto = Object.getPrototypeOf(ts);
+
+ const writableStream = Object.getOwnPropertyDescriptor(proto, "writable");
+ assert(writableStream !== undefined, "it has a writable property");
+ assert(!writableStream.enumerable, "writable should be non-enumerable");
+ assertEquals(
+ typeof writableStream.get,
+ "function",
+ "writable should have a getter"
+ );
+ assertEquals(
+ writableStream.set,
+ undefined,
+ "writable should not have a setter"
+ );
+ assert(writableStream.configurable, "writable should be configurable");
+ assert(
+ ts.writable instanceof WritableStream,
+ "writable is an instance of WritableStream"
+ );
+ assert(
+ WritableStream.prototype.getWriter.call(ts.writable),
+ "writable should pass WritableStream brand check"
+ );
+
+ const readableStream = Object.getOwnPropertyDescriptor(proto, "readable");
+ assert(readableStream !== undefined, "it has a readable property");
+ assert(!readableStream.enumerable, "readable should be non-enumerable");
+ assertEquals(
+ typeof readableStream.get,
+ "function",
+ "readable should have a getter"
+ );
+ assertEquals(
+ readableStream.set,
+ undefined,
+ "readable should not have a setter"
+ );
+ assert(readableStream.configurable, "readable should be configurable");
+ assert(
+ ts.readable instanceof ReadableStream,
+ "readable is an instance of ReadableStream"
+ );
+ assertNotEquals(
+ ReadableStream.prototype.getReader.call(ts.readable),
+ undefined,
+ "readable should pass ReadableStream brand check"
+ );
+});
+
+unitTest(function transformStreamWritableStartsAsWritable() {
+ const ts = new TransformStream({ transform(): void {} });
+
+ const writer = ts.writable.getWriter();
+ assertEquals(writer.desiredSize, 1, "writer.desiredSize should be 1");
+});
+
+unitTest(async function transformStreamReadableCanReadOutOfWritable() {
+ const ts = new TransformStream();
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+ assertEquals(
+ writer.desiredSize,
+ 0,
+ "writer.desiredSize should be 0 after write()"
+ );
+
+ const result = await ts.readable.getReader().read();
+ assertEquals(
+ result.value,
+ "a",
+ "result from reading the readable is the same as was written to writable"
+ );
+ assert(!result.done, "stream should not be done");
+
+ await delay(0);
+ assert(writer.desiredSize === 1, "desiredSize should be 1 again");
+});
+
+unitTest(async function transformStreamCanReadWhatIsWritten() {
+ let c: TransformStreamDefaultController;
+ const ts = new TransformStream({
+ start(controller: TransformStreamDefaultController): void {
+ c = controller;
+ },
+ transform(chunk: string): void {
+ c.enqueue(chunk.toUpperCase());
+ },
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+
+ const result = await ts.readable.getReader().read();
+ assertEquals(
+ result.value,
+ "A",
+ "result from reading the readable is the transformation of what was written to writable"
+ );
+ assert(!result.done, "stream should not be done");
+});
+
+unitTest(async function transformStreamCanReadBothChunks() {
+ let c: TransformStreamDefaultController;
+ const ts = new TransformStream({
+ start(controller: TransformStreamDefaultController): void {
+ c = controller;
+ },
+ transform(chunk: string): void {
+ c.enqueue(chunk.toUpperCase());
+ c.enqueue(chunk.toUpperCase());
+ },
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+
+ const reader = ts.readable.getReader();
+
+ const result1 = await reader.read();
+ assertEquals(
+ result1.value,
+ "A",
+ "the first chunk read is the transformation of the single chunk written"
+ );
+ assert(!result1.done, "stream should not be done");
+
+ const result2 = await reader.read();
+ assertEquals(
+ result2.value,
+ "A",
+ "the second chunk read is also the transformation of the single chunk written"
+ );
+ assert(!result2.done, "stream should not be done");
+});
+
+unitTest(async function transformStreamCanReadWhatIsWritten() {
+ let c: TransformStreamDefaultController;
+ const ts = new TransformStream({
+ start(controller: TransformStreamDefaultController): void {
+ c = controller;
+ },
+ transform(chunk: string): Promise<void> {
+ return delay(0).then(() => c.enqueue(chunk.toUpperCase()));
+ },
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+
+ const result = await ts.readable.getReader().read();
+ assertEquals(
+ result.value,
+ "A",
+ "result from reading the readable is the transformation of what was written to writable"
+ );
+ assert(!result.done, "stream should not be done");
+});
+
+unitTest(async function transformStreamAsyncReadMultipleChunks() {
+ let doSecondEnqueue: () => void;
+ let returnFromTransform: () => void;
+ const ts = new TransformStream({
+ transform(
+ chunk: string,
+ controller: TransformStreamDefaultController
+ ): Promise<void> {
+ delay(0).then(() => controller.enqueue(chunk.toUpperCase()));
+ doSecondEnqueue = (): void => controller.enqueue(chunk.toUpperCase());
+ return new Promise((resolve) => {
+ returnFromTransform = resolve;
+ });
+ },
+ });
+
+ const reader = ts.readable.getReader();
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+
+ const result1 = await reader.read();
+ assertEquals(
+ result1.value,
+ "A",
+ "the first chunk read is the transformation of the single chunk written"
+ );
+ assert(!result1.done, "stream should not be done");
+ doSecondEnqueue!();
+
+ const result2 = await reader.read();
+ assertEquals(
+ result2.value,
+ "A",
+ "the second chunk read is also the transformation of the single chunk written"
+ );
+ assert(!result2.done, "stream should not be done");
+ returnFromTransform!();
+});
+
+unitTest(function transformStreamClosingWriteClosesRead() {
+ const ts = new TransformStream({ transform(): void {} });
+
+ const writer = ts.writable.getWriter();
+ writer.close();
+
+ return Promise.all([writer.closed, ts.readable.getReader().closed]).then(
+ undefined
+ );
+});
+
+unitTest(async function transformStreamCloseWaitAwaitsTransforms() {
+ let transformResolve: () => void;
+ const transformPromise = new Promise<void>((resolve) => {
+ transformResolve = resolve;
+ });
+ const ts = new TransformStream(
+ {
+ transform(): Promise<void> {
+ 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;
+ });
+
+ await delay(0);
+ assertEquals(rsClosed, false, "readable is not closed after a tick");
+ transformResolve!();
+
+ await writer.closed;
+ // TODO: Is this expectation correct?
+ assertEquals(rsClosed, true, "readable is closed at that point");
+});
+
+unitTest(async function transformStreamCloseWriteAfterSyncEnqueues() {
+ let c: TransformStreamDefaultController<string>;
+ const ts = new TransformStream<string, string>({
+ start(controller: TransformStreamDefaultController): void {
+ c = controller;
+ },
+ transform(): Promise<void> {
+ c.enqueue("x");
+ c.enqueue("y");
+ return delay(0);
+ },
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+ writer.close();
+
+ const readableChunks = readableStreamToArray(ts.readable);
+
+ await writer.closed;
+ const chunks = await readableChunks;
+ assertEquals(
+ chunks,
+ ["x", "y"],
+ "both enqueued chunks can be read from the readable"
+ );
+});
+
+unitTest(async function transformStreamWritableCloseAsyncAfterAsyncEnqueues() {
+ let c: TransformStreamDefaultController<string>;
+ const ts = new TransformStream<string, string>({
+ start(controller: TransformStreamDefaultController<string>): void {
+ c = controller;
+ },
+ transform(): Promise<void> {
+ 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);
+
+ await writer.closed;
+ const chunks = await readableChunks;
+ assertEquals(
+ chunks,
+ ["x", "y"],
+ "both enqueued chunks can be read from the readable"
+ );
+});
+
+unitTest(async function transformStreamTransformerMethodsCalledAsMethods() {
+ let c: TransformStreamDefaultController<string>;
+ const transformer = {
+ suffix: "-suffix",
+
+ start(controller: TransformStreamDefaultController<string>): void {
+ c = controller;
+ c.enqueue("start" + this.suffix);
+ },
+
+ transform(chunk: string): void {
+ c.enqueue(chunk + this.suffix);
+ },
+
+ flush(): void {
+ c.enqueue("flushed" + this.suffix);
+ },
+ };
+ const ts = new TransformStream(transformer);
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+ writer.close();
+
+ const readableChunks = readableStreamToArray(ts.readable);
+
+ await writer.closed;
+ const chunks = await readableChunks;
+ assertEquals(
+ chunks,
+ ["start-suffix", "a-suffix", "flushed-suffix"],
+ "all enqueued chunks have suffixes"
+ );
+});
+
+unitTest(async function transformStreamMethodsShouldNotBeAppliedOrCalled() {
+ function functionWithOverloads(): void {}
+ functionWithOverloads.apply = (): void => {
+ throw new Error("apply() should not be called");
+ };
+ functionWithOverloads.call = (): void => {
+ throw new Error("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();
+
+ await readableStreamToArray(ts.readable);
+});
+
+unitTest(async function transformStreamCallTransformSync() {
+ let transformCalled = false;
+ const ts = new TransformStream(
+ {
+ transform(): void {
+ transformCalled = true;
+ },
+ },
+ undefined,
+ { highWaterMark: Infinity }
+ );
+ // transform() is only called synchronously when there is no backpressure and
+ // all microtasks have run.
+ await delay(0);
+ const writePromise = ts.writable.getWriter().write(undefined);
+ assert(transformCalled, "transform() should have been called");
+ await writePromise;
+});
+
+unitTest(function transformStreamCloseWriteCloesesReadWithNoChunks() {
+ const ts = new TransformStream({}, undefined, { highWaterMark: 0 });
+
+ const writer = ts.writable.getWriter();
+ writer.close();
+
+ return Promise.all([writer.closed, ts.readable.getReader().closed]).then(
+ undefined
+ );
+});
+
+unitTest(function transformStreamEnqueueThrowsAfterTerminate() {
+ new TransformStream({
+ start(controller: TransformStreamDefaultController): void {
+ controller.terminate();
+ assertThrows(() => {
+ controller.enqueue(undefined);
+ }, TypeError);
+ },
+ });
+});
+
+unitTest(function transformStreamEnqueueThrowsAfterReadableCancel() {
+ let controller: TransformStreamDefaultController;
+ const ts = new TransformStream({
+ start(c: TransformStreamDefaultController): void {
+ controller = c;
+ },
+ });
+ const cancelPromise = ts.readable.cancel();
+ assertThrows(
+ () => controller.enqueue(undefined),
+ TypeError,
+ undefined,
+ "enqueue should throw"
+ );
+ return cancelPromise;
+});
+
+unitTest(function transformStreamSecondTerminateNoOp() {
+ new TransformStream({
+ start(controller: TransformStreamDefaultController): void {
+ controller.terminate();
+ controller.terminate();
+ },
+ });
+});
+
+unitTest(async function transformStreamTerminateAfterReadableCancelIsNoop() {
+ let controller: TransformStreamDefaultController;
+ const ts = new TransformStream({
+ start(c: TransformStreamDefaultController): void {
+ controller = c;
+ },
+ });
+ const cancelReason = { name: "cancelReason" };
+ const cancelPromise = ts.readable.cancel(cancelReason);
+ controller!.terminate();
+ await cancelPromise;
+ try {
+ await ts.writable.getWriter().closed;
+ } catch (e) {
+ assert(e === cancelReason);
+ return;
+ }
+ throw new Error("closed should have rejected");
+});
+
+unitTest(async function transformStreamStartCalledOnce() {
+ let calls = 0;
+ new TransformStream({
+ start(): void {
+ ++calls;
+ },
+ });
+ await delay(0);
+ assertEquals(calls, 1, "start() should have been called exactly once");
+});
+
+unitTest(function transformStreamReadableTypeThrows() {
+ assertThrows(
+ // eslint-disable-next-line
+ () => new TransformStream({ readableType: "bytes" as any }),
+ RangeError,
+ undefined,
+ "constructor should throw"
+ );
+});
+
+unitTest(function transformStreamWirtableTypeThrows() {
+ assertThrows(
+ // eslint-disable-next-line
+ () => new TransformStream({ writableType: "bytes" as any }),
+ RangeError,
+ undefined,
+ "constructor should throw"
+ );
+});
+
+unitTest(function transformStreamSubclassable() {
+ class Subclass extends TransformStream {
+ extraFunction(): boolean {
+ return true;
+ }
+ }
+ assert(
+ Object.getPrototypeOf(Subclass.prototype) === TransformStream.prototype,
+ "Subclass.prototype's prototype should be TransformStream.prototype"
+ );
+ assert(
+ Object.getPrototypeOf(Subclass) === TransformStream,
+ "Subclass's prototype should be TransformStream"
+ );
+ const sub = new Subclass();
+ assert(
+ sub instanceof TransformStream,
+ "Subclass object should be an instance of TransformStream"
+ );
+ assert(
+ sub instanceof Subclass,
+ "Subclass object should be an instance of Subclass"
+ );
+ const readableGetter = Object.getOwnPropertyDescriptor(
+ TransformStream.prototype,
+ "readable"
+ )!.get;
+ assert(
+ readableGetter!.call(sub) === sub.readable,
+ "Subclass object should pass brand check"
+ );
+ assert(
+ sub.extraFunction(),
+ "extraFunction() should be present on Subclass object"
+ );
+});
diff --git a/cli/js/tests/unit_tests.ts b/cli/js/tests/unit_tests.ts
index 40d0124d3..7327bcc05 100644
--- a/cli/js/tests/unit_tests.ts
+++ b/cli/js/tests/unit_tests.ts
@@ -53,6 +53,7 @@ import "./resources_test.ts";
import "./signal_test.ts";
import "./stat_test.ts";
import "./streams_piping_test.ts";
+import "./streams_transform_test.ts";
import "./streams_writable_test.ts";
import "./symlink_test.ts";
import "./text_encoding_test.ts";