diff options
Diffstat (limited to 'cli/tests')
-rw-r--r-- | cli/tests/integration/js_unit_tests.rs | 1 | ||||
-rw-r--r-- | cli/tests/unit/serve_test.ts | 36 | ||||
-rw-r--r-- | cli/tests/unit/streams_test.ts | 299 | ||||
-rw-r--r-- | cli/tests/unit_node/http_test.ts | 3 |
4 files changed, 322 insertions, 17 deletions
diff --git a/cli/tests/integration/js_unit_tests.rs b/cli/tests/integration/js_unit_tests.rs index c8039f89c..f54280b23 100644 --- a/cli/tests/integration/js_unit_tests.rs +++ b/cli/tests/integration/js_unit_tests.rs @@ -78,6 +78,7 @@ util::unit_test_factory!( signal_test, stat_test, stdio_test, + streams_test, structured_clone_test, symlink_test, sync_test, diff --git a/cli/tests/unit/serve_test.ts b/cli/tests/unit/serve_test.ts index 68d03e846..f0a5b430b 100644 --- a/cli/tests/unit/serve_test.ts +++ b/cli/tests/unit/serve_test.ts @@ -693,24 +693,30 @@ function createStreamTest(count: number, delay: number, action: string) { onError: createOnErrorCb(ac), }); - await listeningPromise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`); - const text = await resp.text(); + try { + await listeningPromise; + const resp = await fetch(`http://127.0.0.1:${servePort}/`); + if (action == "Throw") { + try { + await resp.text(); + fail(); + } catch (_) { + // expected + } + } else { + const text = await resp.text(); - ac.abort(); - await server.finished; - let expected = ""; - if (action == "Throw" && count < 2 && delay < 1000) { - // NOTE: This is specific to the current implementation. In some cases where a stream errors, we - // don't send the first packet. - expected = ""; - } else { - for (let i = 0; i < count; i++) { - expected += `a${i}`; + let expected = ""; + for (let i = 0; i < count; i++) { + expected += `a${i}`; + } + + assertEquals(text, expected); } + } finally { + ac.abort(); + await server.finished; } - - assertEquals(text, expected); }); } diff --git a/cli/tests/unit/streams_test.ts b/cli/tests/unit/streams_test.ts new file mode 100644 index 000000000..4a573c934 --- /dev/null +++ b/cli/tests/unit/streams_test.ts @@ -0,0 +1,299 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { fail } from "https://deno.land/std@v0.42.0/testing/asserts.ts"; +import { assertEquals, Deferred, deferred } from "./test_util.ts"; + +const { + core, + resourceForReadableStream, + // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol +} = Deno[Deno.internal]; + +const LOREM = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + +// Hello world, with optional close +// deno-lint-ignore no-explicit-any +function helloWorldStream(close?: boolean, completion?: Deferred<any>) { + return new ReadableStream({ + start(controller) { + controller.enqueue("hello, world"); + if (close == true) { + controller.close(); + } + }, + cancel(reason) { + completion?.resolve(reason); + }, + }).pipeThrough(new TextEncoderStream()); +} + +// Hello world, with optional close +function errorStream(type: "string" | "controller" | "TypeError") { + return new ReadableStream({ + start(controller) { + controller.enqueue("hello, world"); + }, + pull(controller) { + if (type == "string") { + throw "Uh oh (string)!"; + } + if (type == "TypeError") { + throw TypeError("Uh oh (TypeError)!"); + } + controller.error("Uh oh (controller)!"); + }, + }).pipeThrough(new TextEncoderStream()); +} + +// Long stream with Lorem Ipsum text. +function longStream() { + return new ReadableStream({ + start(controller) { + for (let i = 0; i < 4; i++) { + setTimeout(() => { + controller.enqueue(LOREM); + if (i == 3) { + controller.close(); + } + }, i * 100); + } + }, + }).pipeThrough(new TextEncoderStream()); +} + +// Empty stream, closes either immediately or on a call to pull. +function emptyStream(onPull: boolean) { + return new ReadableStream({ + start(controller) { + if (!onPull) { + controller.close(); + } + }, + pull(controller) { + if (onPull) { + controller.close(); + } + }, + }).pipeThrough(new TextEncoderStream()); +} + +// Include an empty chunk +function emptyChunkStream() { + return new ReadableStream({ + start(controller) { + controller.enqueue(new Uint8Array([1])); + controller.enqueue(new Uint8Array([])); + controller.enqueue(new Uint8Array([2])); + controller.close(); + }, + }); +} + +// Creates a stream with the given number of packets, a configurable delay between packets, and a final +// action (either "Throw" or "Close"). +function makeStreamWithCount( + count: number, + delay: number, + action: "Throw" | "Close", +): ReadableStream { + function doAction(controller: ReadableStreamDefaultController, i: number) { + if (i == count) { + if (action == "Throw") { + controller.error(new Error("Expected error!")); + } else { + controller.close(); + } + } else { + controller.enqueue(String.fromCharCode("a".charCodeAt(0) + i)); + + if (delay == 0) { + doAction(controller, i + 1); + } else { + setTimeout(() => doAction(controller, i + 1), delay); + } + } + } + + return new ReadableStream({ + start(controller) { + if (delay == 0) { + doAction(controller, 0); + } else { + setTimeout(() => doAction(controller, 0), delay); + } + }, + }).pipeThrough(new TextEncoderStream()); +} + +// Normal stream operation +Deno.test(async function readableStream() { + const rid = resourceForReadableStream(helloWorldStream()); + const buffer = new Uint8Array(1024); + const nread = await core.ops.op_read(rid, buffer); + assertEquals(nread, 12); + core.ops.op_close(rid); +}); + +// Close the stream after reading everything +Deno.test(async function readableStreamClose() { + const cancel = deferred(); + const rid = resourceForReadableStream(helloWorldStream(false, cancel)); + const buffer = new Uint8Array(1024); + const nread = await core.ops.op_read(rid, buffer); + assertEquals(nread, 12); + core.ops.op_close(rid); + assertEquals(await cancel, undefined); +}); + +// Close the stream without reading everything +Deno.test(async function readableStreamClosePartialRead() { + const cancel = deferred(); + const rid = resourceForReadableStream(helloWorldStream(false, cancel)); + const buffer = new Uint8Array(5); + const nread = await core.ops.op_read(rid, buffer); + assertEquals(nread, 5); + core.ops.op_close(rid); + assertEquals(await cancel, undefined); +}); + +// Close the stream without reading anything +Deno.test(async function readableStreamCloseWithoutRead() { + const cancel = deferred(); + const rid = resourceForReadableStream(helloWorldStream(false, cancel)); + core.ops.op_close(rid); + assertEquals(await cancel, undefined); +}); + +Deno.test(async function readableStreamPartial() { + const rid = resourceForReadableStream(helloWorldStream()); + const buffer = new Uint8Array(5); + const nread = await core.ops.op_read(rid, buffer); + assertEquals(nread, 5); + const buffer2 = new Uint8Array(1024); + const nread2 = await core.ops.op_read(rid, buffer2); + assertEquals(nread2, 7); + core.ops.op_close(rid); +}); + +Deno.test(async function readableStreamLongReadAll() { + const rid = resourceForReadableStream(longStream()); + const buffer = await core.ops.op_read_all(rid); + assertEquals(buffer.length, LOREM.length * 4); + core.ops.op_close(rid); +}); + +Deno.test(async function readableStreamLongByPiece() { + const rid = resourceForReadableStream(longStream()); + let total = 0; + for (let i = 0; i < 100; i++) { + const length = await core.ops.op_read(rid, new Uint8Array(16)); + total += length; + if (length == 0) { + break; + } + } + assertEquals(total, LOREM.length * 4); + core.ops.op_close(rid); +}); + +for ( + const type of [ + "string", + "TypeError", + "controller", + ] as ("string" | "TypeError" | "controller")[] +) { + Deno.test(`readableStreamError_${type}`, async function () { + const rid = resourceForReadableStream(errorStream(type)); + assertEquals(12, await core.ops.op_read(rid, new Uint8Array(16))); + try { + await core.ops.op_read(rid, new Uint8Array(1)); + fail(); + } catch (e) { + assertEquals(e.message, `Uh oh (${type})!`); + } + core.ops.op_close(rid); + }); +} + +Deno.test(async function readableStreamEmptyOnStart() { + const rid = resourceForReadableStream(emptyStream(true)); + const buffer = new Uint8Array(1024); + const nread = await core.ops.op_read(rid, buffer); + assertEquals(nread, 0); + core.ops.op_close(rid); +}); + +Deno.test(async function readableStreamEmptyOnPull() { + const rid = resourceForReadableStream(emptyStream(false)); + const buffer = new Uint8Array(1024); + const nread = await core.ops.op_read(rid, buffer); + assertEquals(nread, 0); + core.ops.op_close(rid); +}); + +Deno.test(async function readableStreamEmptyReadAll() { + const rid = resourceForReadableStream(emptyStream(false)); + const buffer = await core.ops.op_read_all(rid); + assertEquals(buffer.length, 0); + core.ops.op_close(rid); +}); + +Deno.test(async function readableStreamWithEmptyChunk() { + const rid = resourceForReadableStream(emptyChunkStream()); + const buffer = await core.ops.op_read_all(rid); + assertEquals(buffer, new Uint8Array([1, 2])); + core.ops.op_close(rid); +}); + +Deno.test(async function readableStreamWithEmptyChunkOneByOne() { + const rid = resourceForReadableStream(emptyChunkStream()); + assertEquals(1, await core.ops.op_read(rid, new Uint8Array(1))); + assertEquals(1, await core.ops.op_read(rid, new Uint8Array(1))); + assertEquals(0, await core.ops.op_read(rid, new Uint8Array(1))); + core.ops.op_close(rid); +}); + +for (const count of [0, 1, 2, 3]) { + for (const delay of [0, 1, 10]) { + // Creating a stream that errors in start will throw + if (delay > 0) { + createStreamTest(count, delay, "Throw"); + } + createStreamTest(count, delay, "Close"); + } +} + +function createStreamTest( + count: number, + delay: number, + action: "Throw" | "Close", +) { + Deno.test(`streamCount${count}Delay${delay}${action}`, async () => { + let rid; + try { + rid = resourceForReadableStream( + makeStreamWithCount(count, delay, action), + ); + for (let i = 0; i < count; i++) { + const buffer = new Uint8Array(1); + await core.ops.op_read(rid, buffer); + } + if (action == "Throw") { + try { + const buffer = new Uint8Array(1); + assertEquals(1, await core.ops.op_read(rid, buffer)); + fail(); + } catch (e) { + // We expect this to be thrown + assertEquals(e.message, "Expected error!"); + } + } else { + const buffer = new Uint8Array(1); + assertEquals(0, await core.ops.op_read(rid, buffer)); + } + } finally { + core.ops.op_close(rid); + } + }); +} diff --git a/cli/tests/unit_node/http_test.ts b/cli/tests/unit_node/http_test.ts index a361ff257..706c672f1 100644 --- a/cli/tests/unit_node/http_test.ts +++ b/cli/tests/unit_node/http_test.ts @@ -141,8 +141,7 @@ Deno.test("[node/http] chunked response", async () => { } }); -// TODO(kt3k): This test case exercises the workaround for https://github.com/denoland/deno/issues/17194 -// This should be removed when #17194 is resolved. +// Test empty chunks: https://github.com/denoland/deno/issues/17194 Deno.test("[node/http] empty chunk in the middle of response", async () => { const promise = deferred<void>(); |