diff options
Diffstat (limited to 'cli/tests/unit')
102 files changed, 0 insertions, 36382 deletions
diff --git a/cli/tests/unit/README.md b/cli/tests/unit/README.md deleted file mode 100644 index af31c08fc..000000000 --- a/cli/tests/unit/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Deno runtime tests - -Files in this directory are unit tests for Deno runtime. - -Testing Deno runtime code requires checking API under different runtime -permissions. To accomplish this all tests exercised are created using -`Deno.test()` function. - -```ts -import {} from "./test_util.ts"; - -Deno.test(function simpleTestFn(): void { - // test code here -}); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - function complexTestFn(): void { - // test code here - }, -); -``` - -## Running tests - -There are two ways to run `unit_test_runner.ts`: - -```sh -# Run all tests. -cargo run --bin deno -- test --allow-all --unstable --location=http://js-unit-tests/foo/bar cli/tests/unit/ - -# Run a specific test module -cargo run --bin deno -- test --allow-all --unstable --location=http://js-unit-tests/foo/bar cli/tests/unit/files_test.ts -``` - -### Http server - -`target/debug/test_server` is required to run when one's running unit tests. -During CI it's spawned automatically, but if you want to run tests manually make -sure that server is spawned otherwise there'll be cascade of test failures. diff --git a/cli/tests/unit/abort_controller_test.ts b/cli/tests/unit/abort_controller_test.ts deleted file mode 100644 index 60ea6aa24..000000000 --- a/cli/tests/unit/abort_controller_test.ts +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assert, assertEquals } from "./test_util.ts"; - -Deno.test(function basicAbortController() { - const controller = new AbortController(); - assert(controller); - const { signal } = controller; - assert(signal); - assertEquals(signal.aborted, false); - controller.abort(); - assertEquals(signal.aborted, true); -}); - -Deno.test(function signalCallsOnabort() { - const controller = new AbortController(); - const { signal } = controller; - let called = false; - signal.onabort = (evt) => { - assert(evt); - assertEquals(evt.type, "abort"); - called = true; - }; - controller.abort(); - assert(called); -}); - -Deno.test(function signalEventListener() { - const controller = new AbortController(); - const { signal } = controller; - let called = false; - signal.addEventListener("abort", function (ev) { - assert(this === signal); - assertEquals(ev.type, "abort"); - called = true; - }); - controller.abort(); - assert(called); -}); - -Deno.test(function onlyAbortsOnce() { - const controller = new AbortController(); - const { signal } = controller; - let called = 0; - signal.addEventListener("abort", () => called++); - signal.onabort = () => { - called++; - }; - controller.abort(); - assertEquals(called, 2); - controller.abort(); - assertEquals(called, 2); -}); - -Deno.test(function controllerHasProperToString() { - const actual = Object.prototype.toString.call(new AbortController()); - assertEquals(actual, "[object AbortController]"); -}); - -Deno.test(function abortReason() { - const signal = AbortSignal.abort("hey!"); - assertEquals(signal.aborted, true); - assertEquals(signal.reason, "hey!"); -}); diff --git a/cli/tests/unit/blob_test.ts b/cli/tests/unit/blob_test.ts deleted file mode 100644 index e6623a65c..000000000 --- a/cli/tests/unit/blob_test.ts +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals, assertStringIncludes } from "./test_util.ts"; -import { concat } from "@test_util/std/bytes/concat.ts"; - -Deno.test(function blobString() { - const b1 = new Blob(["Hello World"]); - const str = "Test"; - const b2 = new Blob([b1, str]); - assertEquals(b2.size, b1.size + str.length); -}); - -Deno.test(function blobBuffer() { - const buffer = new ArrayBuffer(12); - const u8 = new Uint8Array(buffer); - const f1 = new Float32Array(buffer); - const b1 = new Blob([buffer, u8]); - assertEquals(b1.size, 2 * u8.length); - const b2 = new Blob([b1, f1]); - assertEquals(b2.size, 3 * u8.length); -}); - -Deno.test(function blobSlice() { - const blob = new Blob(["Deno", "Foo"]); - const b1 = blob.slice(0, 3, "Text/HTML"); - assert(b1 instanceof Blob); - assertEquals(b1.size, 3); - assertEquals(b1.type, "text/html"); - const b2 = blob.slice(-1, 3); - assertEquals(b2.size, 0); - const b3 = blob.slice(100, 3); - assertEquals(b3.size, 0); - const b4 = blob.slice(0, 10); - assertEquals(b4.size, blob.size); -}); - -Deno.test(function blobInvalidType() { - const blob = new Blob(["foo"], { - type: "\u0521", - }); - - assertEquals(blob.type, ""); -}); - -Deno.test(function blobShouldNotThrowError() { - let hasThrown = false; - - try { - // deno-lint-ignore no-explicit-any - const options1: any = { - ending: "utf8", - hasOwnProperty: "hasOwnProperty", - }; - const options2 = Object.create(null); - new Blob(["Hello World"], options1); - new Blob(["Hello World"], options2); - } catch { - hasThrown = true; - } - - assertEquals(hasThrown, false); -}); - -/* TODO https://github.com/denoland/deno/issues/7540 -Deno.test(function nativeEndLine() { - const options = { - ending: "native", - } as const; - const blob = new Blob(["Hello\nWorld"], options); - - assertEquals(blob.size, Deno.build.os === "windows" ? 12 : 11); -}); -*/ - -Deno.test(async function blobText() { - const blob = new Blob(["Hello World"]); - assertEquals(await blob.text(), "Hello World"); -}); - -Deno.test(async function blobStream() { - const blob = new Blob(["Hello World"]); - const stream = blob.stream(); - assert(stream instanceof ReadableStream); - const reader = stream.getReader(); - let bytes = new Uint8Array(); - const read = async (): Promise<void> => { - const { done, value } = await reader.read(); - if (!done && value) { - bytes = concat(bytes, value); - return read(); - } - }; - await read(); - const decoder = new TextDecoder(); - assertEquals(decoder.decode(bytes), "Hello World"); -}); - -Deno.test(async function blobArrayBuffer() { - const uint = new Uint8Array([102, 111, 111]); - const blob = new Blob([uint]); - assertEquals(await blob.arrayBuffer(), uint.buffer); -}); - -Deno.test(function blobConstructorNameIsBlob() { - const blob = new Blob(); - assertEquals(blob.constructor.name, "Blob"); -}); - -Deno.test(function blobCustomInspectFunction() { - const blob = new Blob(); - assertEquals( - Deno.inspect(blob), - `Blob { size: 0, type: "" }`, - ); - assertStringIncludes(Deno.inspect(Blob.prototype), "Blob"); -}); diff --git a/cli/tests/unit/body_test.ts b/cli/tests/unit/body_test.ts deleted file mode 100644 index 18cdb22be..000000000 --- a/cli/tests/unit/body_test.ts +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals } from "./test_util.ts"; - -// just a hack to get a body object -// deno-lint-ignore no-explicit-any -function buildBody(body: any, headers?: Headers): Body { - const stub = new Request("http://foo/", { - body: body, - headers, - method: "POST", - }); - return stub as Body; -} - -const intArrays = [ - Int8Array, - Int16Array, - Int32Array, - Uint8Array, - Uint16Array, - Uint32Array, - Uint8ClampedArray, - Float32Array, - Float64Array, -]; -Deno.test(async function arrayBufferFromByteArrays() { - const buffer = new TextEncoder().encode("ahoyhoy8").buffer; - - for (const type of intArrays) { - const body = buildBody(new type(buffer)); - const text = new TextDecoder("utf-8").decode(await body.arrayBuffer()); - assertEquals(text, "ahoyhoy8"); - } -}); - -//FormData -Deno.test( - { permissions: { net: true } }, - async function bodyMultipartFormData() { - const response = await fetch( - "http://localhost:4545/multipart_form_data.txt", - ); - assert(response.body instanceof ReadableStream); - - const text = await response.text(); - - const body = buildBody(text, response.headers); - - const formData = await body.formData(); - assert(formData.has("field_1")); - assertEquals(formData.get("field_1")!.toString(), "value_1 \r\n"); - assert(formData.has("field_2")); - }, -); - -// FormData: non-ASCII names and filenames -Deno.test( - { permissions: { net: true } }, - async function bodyMultipartFormDataNonAsciiNames() { - const boundary = "----01230123"; - const payload = [ - `--${boundary}`, - `Content-Disposition: form-data; name="文字"`, - "", - "文字", - `--${boundary}`, - `Content-Disposition: form-data; name="file"; filename="文字"`, - "Content-Type: application/octet-stream", - "", - "", - `--${boundary}--`, - ].join("\r\n"); - - const body = buildBody( - new TextEncoder().encode(payload), - new Headers({ - "Content-Type": `multipart/form-data; boundary=${boundary}`, - }), - ); - - const formData = await body.formData(); - assert(formData.has("文字")); - assertEquals(formData.get("文字"), "文字"); - assert(formData.has("file")); - assert(formData.get("file") instanceof File); - assertEquals((formData.get("file") as File).name, "文字"); - }, -); - -// FormData: non-ASCII names and filenames roundtrip -Deno.test( - { permissions: { net: true } }, - async function bodyMultipartFormDataNonAsciiRoundtrip() { - const inFormData = new FormData(); - inFormData.append("文字", "文字"); - inFormData.append("file", new File([], "文字")); - - const body = buildBody(inFormData); - - const formData = await body.formData(); - assert(formData.has("文字")); - assertEquals(formData.get("文字"), "文字"); - assert(formData.has("file")); - assert(formData.get("file") instanceof File); - assertEquals((formData.get("file") as File).name, "文字"); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function bodyURLEncodedFormData() { - const response = await fetch( - "http://localhost:4545/subdir/form_urlencoded.txt", - ); - assert(response.body instanceof ReadableStream); - - const text = await response.text(); - - const body = buildBody(text, response.headers); - - const formData = await body.formData(); - assert(formData.has("field_1")); - assertEquals(formData.get("field_1")!.toString(), "Hi"); - assert(formData.has("field_2")); - assertEquals(formData.get("field_2")!.toString(), "<Deno>"); - }, -); - -Deno.test({ permissions: {} }, async function bodyURLSearchParams() { - const body = buildBody(new URLSearchParams({ hello: "world" })); - - const text = await body.text(); - assertEquals(text, "hello=world"); -}); - -Deno.test(async function bodyArrayBufferMultipleParts() { - const parts: Uint8Array[] = []; - let size = 0; - for (let i = 0; i <= 150000; i++) { - const part = new Uint8Array([1]); - parts.push(part); - size += part.length; - } - - let offset = 0; - const stream = new ReadableStream({ - pull(controller) { - // parts.shift() takes forever: https://github.com/denoland/deno/issues/5259 - const chunk = parts[offset++]; - if (!chunk) return controller.close(); - controller.enqueue(chunk); - }, - }); - - const body = buildBody(stream); - assertEquals((await body.arrayBuffer()).byteLength, size); -}); - -// https://github.com/denoland/deno/issues/20793 -Deno.test( - { permissions: { net: true } }, - async function bodyMultipartFormDataMultipleHeaders() { - const boundary = "----formdata-polyfill-0.970665446687947"; - const payload = [ - "------formdata-polyfill-0.970665446687947", - 'Content-Disposition: form-data; name="x"; filename="blob"', - "Content-Length: 1", - "Content-Type: application/octet-stream", - "last-modified: Wed, 04 Oct 2023 20:28:45 GMT", - "", - "y", - "------formdata-polyfill-0.970665446687947--", - ].join("\r\n"); - - const body = buildBody( - new TextEncoder().encode(payload), - new Headers({ - "Content-Type": `multipart/form-data; boundary=${boundary}`, - }), - ); - - const formData = await body.formData(); - const file = formData.get("x"); - assert(file instanceof File); - const text = await file.text(); - assertEquals(text, "y"); - assertEquals(file.size, 1); - }, -); diff --git a/cli/tests/unit/broadcast_channel_test.ts b/cli/tests/unit/broadcast_channel_test.ts deleted file mode 100644 index c5d7f7e7f..000000000 --- a/cli/tests/unit/broadcast_channel_test.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "@test_util/std/assert/mod.ts"; - -Deno.test("BroadcastChannel worker", async () => { - const intercom = new BroadcastChannel("intercom"); - let count = 0; - - const url = import.meta.resolve( - "../testdata/workers/broadcast_channel.ts", - ); - const worker = new Worker(url, { type: "module", name: "worker" }); - worker.onmessage = () => intercom.postMessage(++count); - - const { promise, resolve } = Promise.withResolvers<void>(); - - intercom.onmessage = function (e) { - assertEquals(count, e.data); - if (count < 42) { - intercom.postMessage(++count); - } else { - worker.terminate(); - intercom.close(); - resolve(); - } - }; - - await promise; -}); - -Deno.test("BroadcastChannel immediate close after post", () => { - const bc = new BroadcastChannel("internal_notification"); - bc.postMessage("New listening connected!"); - bc.close(); -}); diff --git a/cli/tests/unit/buffer_test.ts b/cli/tests/unit/buffer_test.ts deleted file mode 100644 index 9d7e51a95..000000000 --- a/cli/tests/unit/buffer_test.ts +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -// deno-lint-ignore-file no-deprecated-deno-api - -// This code has been ported almost directly from Go's src/bytes/buffer_test.go -// Copyright 2009 The Go Authors. All rights reserved. BSD license. -// https://github.com/golang/go/blob/master/LICENSE -import { - assert, - assertEquals, - assertRejects, - assertThrows, -} from "./test_util.ts"; - -const MAX_SIZE = 2 ** 32 - 2; -// N controls how many iterations of certain checks are performed. -const N = 100; -let testBytes: Uint8Array | null; -let testString: string | null; - -const ignoreMaxSizeTests = true; - -function init() { - if (testBytes == null) { - testBytes = new Uint8Array(N); - for (let i = 0; i < N; i++) { - testBytes[i] = "a".charCodeAt(0) + (i % 26); - } - const decoder = new TextDecoder(); - testString = decoder.decode(testBytes); - } -} - -function check(buf: Deno.Buffer, s: string) { - const bytes = buf.bytes(); - assertEquals(buf.length, bytes.byteLength); - const decoder = new TextDecoder(); - const bytesStr = decoder.decode(bytes); - assertEquals(bytesStr, s); - assertEquals(buf.length, s.length); -} - -// Fill buf through n writes of byte slice fub. -// The initial contents of buf corresponds to the string s; -// the result is the final contents of buf returned as a string. -async function fillBytes( - buf: Deno.Buffer, - s: string, - n: number, - fub: Uint8Array, -): Promise<string> { - check(buf, s); - for (; n > 0; n--) { - const m = await buf.write(fub); - assertEquals(m, fub.byteLength); - const decoder = new TextDecoder(); - s += decoder.decode(fub); - check(buf, s); - } - return s; -} - -// Empty buf through repeated reads into fub. -// The initial contents of buf corresponds to the string s. -async function empty( - buf: Deno.Buffer, - s: string, - fub: Uint8Array, -) { - check(buf, s); - while (true) { - const r = await buf.read(fub); - if (r === null) { - break; - } - s = s.slice(r); - check(buf, s); - } - check(buf, ""); -} - -function repeat(c: string, bytes: number): Uint8Array { - assertEquals(c.length, 1); - const ui8 = new Uint8Array(bytes); - ui8.fill(c.charCodeAt(0)); - return ui8; -} - -Deno.test(function bufferNewBuffer() { - init(); - assert(testBytes); - assert(testString); - const buf = new Deno.Buffer(testBytes.buffer as ArrayBuffer); - check(buf, testString); -}); - -Deno.test(async function bufferBasicOperations() { - init(); - assert(testBytes); - assert(testString); - const buf = new Deno.Buffer(); - for (let i = 0; i < 5; i++) { - check(buf, ""); - - buf.reset(); - check(buf, ""); - - buf.truncate(0); - check(buf, ""); - - let n = await buf.write(testBytes.subarray(0, 1)); - assertEquals(n, 1); - check(buf, "a"); - - n = await buf.write(testBytes.subarray(1, 2)); - assertEquals(n, 1); - check(buf, "ab"); - - n = await buf.write(testBytes.subarray(2, 26)); - assertEquals(n, 24); - check(buf, testString.slice(0, 26)); - - buf.truncate(26); - check(buf, testString.slice(0, 26)); - - buf.truncate(20); - check(buf, testString.slice(0, 20)); - - await empty(buf, testString.slice(0, 20), new Uint8Array(5)); - await empty(buf, "", new Uint8Array(100)); - - // TODO(bartlomieju): buf.writeByte() - // TODO(bartlomieju): buf.readByte() - } -}); - -Deno.test(async function bufferReadEmptyAtEOF() { - // check that EOF of 'buf' is not reached (even though it's empty) if - // results are written to buffer that has 0 length (ie. it can't store any data) - const buf = new Deno.Buffer(); - const zeroLengthTmp = new Uint8Array(0); - const result = await buf.read(zeroLengthTmp); - assertEquals(result, 0); -}); - -Deno.test(async function bufferLargeByteWrites() { - init(); - const buf = new Deno.Buffer(); - const limit = 9; - for (let i = 3; i < limit; i += 3) { - const s = await fillBytes(buf, "", 5, testBytes!); - await empty(buf, s, new Uint8Array(Math.floor(testString!.length / i))); - } - check(buf, ""); -}); - -Deno.test(async function bufferTooLargeByteWrites() { - init(); - const tmp = new Uint8Array(72); - const growLen = Number.MAX_VALUE; - const xBytes = repeat("x", 0); - const buf = new Deno.Buffer(xBytes.buffer as ArrayBuffer); - await buf.read(tmp); - - assertThrows( - () => { - buf.grow(growLen); - }, - Error, - "grown beyond the maximum size", - ); -}); - -Deno.test( - { ignore: ignoreMaxSizeTests }, - function bufferGrowWriteMaxBuffer() { - const bufSize = 16 * 1024; - const capacities = [MAX_SIZE, MAX_SIZE - 1]; - for (const capacity of capacities) { - let written = 0; - const buf = new Deno.Buffer(); - const writes = Math.floor(capacity / bufSize); - for (let i = 0; i < writes; i++) { - written += buf.writeSync(repeat("x", bufSize)); - } - - if (written < capacity) { - written += buf.writeSync(repeat("x", capacity - written)); - } - - assertEquals(written, capacity); - } - }, -); - -Deno.test( - { ignore: ignoreMaxSizeTests }, - async function bufferGrowReadCloseMaxBufferPlus1() { - const reader = new Deno.Buffer(new ArrayBuffer(MAX_SIZE + 1)); - const buf = new Deno.Buffer(); - - await assertRejects( - async () => { - await buf.readFrom(reader); - }, - Error, - "grown beyond the maximum size", - ); - }, -); - -Deno.test( - { ignore: ignoreMaxSizeTests }, - function bufferGrowReadSyncCloseMaxBufferPlus1() { - const reader = new Deno.Buffer(new ArrayBuffer(MAX_SIZE + 1)); - const buf = new Deno.Buffer(); - - assertThrows( - () => { - buf.readFromSync(reader); - }, - Error, - "grown beyond the maximum size", - ); - }, -); - -Deno.test( - { ignore: ignoreMaxSizeTests }, - function bufferGrowReadSyncCloseToMaxBuffer() { - const capacities = [MAX_SIZE, MAX_SIZE - 1]; - for (const capacity of capacities) { - const reader = new Deno.Buffer(new ArrayBuffer(capacity)); - const buf = new Deno.Buffer(); - buf.readFromSync(reader); - - assertEquals(buf.length, capacity); - } - }, -); - -Deno.test( - { ignore: ignoreMaxSizeTests }, - async function bufferGrowReadCloseToMaxBuffer() { - const capacities = [MAX_SIZE, MAX_SIZE - 1]; - for (const capacity of capacities) { - const reader = new Deno.Buffer(new ArrayBuffer(capacity)); - const buf = new Deno.Buffer(); - await buf.readFrom(reader); - assertEquals(buf.length, capacity); - } - }, -); - -Deno.test( - { ignore: ignoreMaxSizeTests }, - async function bufferReadCloseToMaxBufferWithInitialGrow() { - const capacities = [MAX_SIZE, MAX_SIZE - 1, MAX_SIZE - 512]; - for (const capacity of capacities) { - const reader = new Deno.Buffer(new ArrayBuffer(capacity)); - const buf = new Deno.Buffer(); - buf.grow(MAX_SIZE); - await buf.readFrom(reader); - assertEquals(buf.length, capacity); - } - }, -); - -Deno.test(async function bufferLargeByteReads() { - init(); - assert(testBytes); - assert(testString); - const buf = new Deno.Buffer(); - for (let i = 3; i < 30; i += 3) { - const n = Math.floor(testBytes.byteLength / i); - const s = await fillBytes(buf, "", 5, testBytes.subarray(0, n)); - await empty(buf, s, new Uint8Array(testString.length)); - } - check(buf, ""); -}); - -Deno.test(function bufferCapWithPreallocatedSlice() { - const buf = new Deno.Buffer(new ArrayBuffer(10)); - assertEquals(buf.capacity, 10); -}); - -Deno.test(async function bufferReadFrom() { - init(); - assert(testBytes); - assert(testString); - const buf = new Deno.Buffer(); - for (let i = 3; i < 30; i += 3) { - const s = await fillBytes( - buf, - "", - 5, - testBytes.subarray(0, Math.floor(testBytes.byteLength / i)), - ); - const b = new Deno.Buffer(); - await b.readFrom(buf); - const fub = new Uint8Array(testString.length); - await empty(b, s, fub); - } - await assertRejects(async function () { - await new Deno.Buffer().readFrom(null!); - }); -}); - -Deno.test(async function bufferReadFromSync() { - init(); - assert(testBytes); - assert(testString); - const buf = new Deno.Buffer(); - for (let i = 3; i < 30; i += 3) { - const s = await fillBytes( - buf, - "", - 5, - testBytes.subarray(0, Math.floor(testBytes.byteLength / i)), - ); - const b = new Deno.Buffer(); - b.readFromSync(buf); - const fub = new Uint8Array(testString.length); - await empty(b, s, fub); - } - assertThrows(function () { - new Deno.Buffer().readFromSync(null!); - }); -}); - -Deno.test(async function bufferTestGrow() { - const tmp = new Uint8Array(72); - for (const startLen of [0, 100, 1000, 10000]) { - const xBytes = repeat("x", startLen); - for (const growLen of [0, 100, 1000, 10000]) { - const buf = new Deno.Buffer(xBytes.buffer as ArrayBuffer); - // If we read, this affects buf.off, which is good to test. - const nread = (await buf.read(tmp)) ?? 0; - buf.grow(growLen); - const yBytes = repeat("y", growLen); - await buf.write(yBytes); - // Check that buffer has correct data. - assertEquals( - buf.bytes().subarray(0, startLen - nread), - xBytes.subarray(nread), - ); - assertEquals( - buf.bytes().subarray(startLen - nread, startLen - nread + growLen), - yBytes, - ); - } - } -}); - -Deno.test(async function testReadAll() { - init(); - assert(testBytes); - const reader = new Deno.Buffer(testBytes.buffer as ArrayBuffer); - const actualBytes = await Deno.readAll(reader); - assertEquals(testBytes.byteLength, actualBytes.byteLength); - for (let i = 0; i < testBytes.length; ++i) { - assertEquals(testBytes[i], actualBytes[i]); - } -}); - -Deno.test(function testReadAllSync() { - init(); - assert(testBytes); - const reader = new Deno.Buffer(testBytes.buffer as ArrayBuffer); - const actualBytes = Deno.readAllSync(reader); - assertEquals(testBytes.byteLength, actualBytes.byteLength); - for (let i = 0; i < testBytes.length; ++i) { - assertEquals(testBytes[i], actualBytes[i]); - } -}); - -Deno.test(async function testWriteAll() { - init(); - assert(testBytes); - const writer = new Deno.Buffer(); - await Deno.writeAll(writer, testBytes); - const actualBytes = writer.bytes(); - assertEquals(testBytes.byteLength, actualBytes.byteLength); - for (let i = 0; i < testBytes.length; ++i) { - assertEquals(testBytes[i], actualBytes[i]); - } -}); - -Deno.test(function testWriteAllSync() { - init(); - assert(testBytes); - const writer = new Deno.Buffer(); - Deno.writeAllSync(writer, testBytes); - const actualBytes = writer.bytes(); - assertEquals(testBytes.byteLength, actualBytes.byteLength); - for (let i = 0; i < testBytes.length; ++i) { - assertEquals(testBytes[i], actualBytes[i]); - } -}); - -Deno.test(function testBufferBytesArrayBufferLength() { - // defaults to copy - const args = [{}, { copy: undefined }, undefined, { copy: true }]; - for (const arg of args) { - const bufSize = 64 * 1024; - const bytes = new TextEncoder().encode("a".repeat(bufSize)); - const reader = new Deno.Buffer(); - Deno.writeAllSync(reader, bytes); - - const writer = new Deno.Buffer(); - writer.readFromSync(reader); - const actualBytes = writer.bytes(arg); - - assertEquals(actualBytes.byteLength, bufSize); - assert(actualBytes.buffer !== writer.bytes(arg).buffer); - assertEquals(actualBytes.byteLength, actualBytes.buffer.byteLength); - } -}); - -Deno.test(function testBufferBytesCopyFalse() { - const bufSize = 64 * 1024; - const bytes = new TextEncoder().encode("a".repeat(bufSize)); - const reader = new Deno.Buffer(); - Deno.writeAllSync(reader, bytes); - - const writer = new Deno.Buffer(); - writer.readFromSync(reader); - const actualBytes = writer.bytes({ copy: false }); - - assertEquals(actualBytes.byteLength, bufSize); - assertEquals(actualBytes.buffer, writer.bytes({ copy: false }).buffer); - assert(actualBytes.buffer.byteLength > actualBytes.byteLength); -}); - -Deno.test(function testBufferBytesCopyFalseGrowExactBytes() { - const bufSize = 64 * 1024; - const bytes = new TextEncoder().encode("a".repeat(bufSize)); - const reader = new Deno.Buffer(); - Deno.writeAllSync(reader, bytes); - - const writer = new Deno.Buffer(); - writer.grow(bufSize); - writer.readFromSync(reader); - const actualBytes = writer.bytes({ copy: false }); - - assertEquals(actualBytes.byteLength, bufSize); - assertEquals(actualBytes.buffer.byteLength, actualBytes.byteLength); -}); - -Deno.test(function testThrowsErrorWhenBufferExceedsMaxLength() { - const kStringMaxLengthPlusOne = 536870888 + 1; - const bytes = new Uint8Array(kStringMaxLengthPlusOne); - - assertThrows( - () => { - new TextDecoder().decode(bytes); - }, - TypeError, - "buffer exceeds maximum length", - ); -}); diff --git a/cli/tests/unit/build_test.ts b/cli/tests/unit/build_test.ts deleted file mode 100644 index f697b64d3..000000000 --- a/cli/tests/unit/build_test.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert } from "./test_util.ts"; - -Deno.test(function buildInfo() { - // Deno.build is injected by rollup at compile time. Here - // we check it has been properly transformed. - const { arch, os } = Deno.build; - assert(arch.length > 0); - assert(os === "darwin" || os === "windows" || os === "linux"); -}); diff --git a/cli/tests/unit/cache_api_test.ts b/cli/tests/unit/cache_api_test.ts deleted file mode 100644 index 792929870..000000000 --- a/cli/tests/unit/cache_api_test.ts +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertFalse, - assertRejects, - assertThrows, -} from "./test_util.ts"; - -Deno.test(async function cacheStorage() { - const cacheName = "cache-v1"; - const _cache = await caches.open(cacheName); - assert(await caches.has(cacheName)); - assert(await caches.delete(cacheName)); - assertFalse(await caches.has(cacheName)); -}); - -Deno.test(async function cacheApi() { - const cacheName = "cache-v1"; - const cache = await caches.open(cacheName); - // Test cache.put() with url string as key. - { - const req = "https://deno.com"; - await cache.put(req, new Response("deno.com - key is string")); - const res = await cache.match(req); - assertEquals(await res?.text(), "deno.com - key is string"); - assert(await cache.delete(req)); - } - // Test cache.put() with url instance as key. - { - const req = new URL("https://deno.com"); - await cache.put(req, new Response("deno.com - key is URL")); - const res = await cache.match(req); - assertEquals(await res?.text(), "deno.com - key is URL"); - assert(await cache.delete(req)); - } - // Test cache.put() with request instance as key. - { - const req = new Request("https://deno.com"); - await cache.put(req, new Response("deno.com - key is Request")); - const res = await cache.match(req); - assertEquals(await res?.text(), "deno.com - key is Request"); - assert(await cache.delete(req)); - } - - // Test cache.put() throws with response Vary header set to *. - { - const req = new Request("https://deno.com"); - assertRejects( - async () => { - await cache.put( - req, - new Response("deno.com - key is Request", { - headers: { Vary: "*" }, - }), - ); - }, - TypeError, - "Vary header must not contain '*'", - ); - } - - // Test cache.match() with same url but different values for Vary header. - { - await cache.put( - new Request("https://example.com/", { - headers: { - "Accept": "application/json", - }, - }), - Response.json({ msg: "hello world" }, { - headers: { - "Content-Type": "application/json", - "Vary": "Accept", - }, - }), - ); - const res = await cache.match("https://example.com/"); - assertEquals(res, undefined); - const res2 = await cache.match( - new Request("https://example.com/", { - headers: { "Accept": "text/html" }, - }), - ); - assertEquals(res2, undefined); - - const res3 = await cache.match( - new Request("https://example.com/", { - headers: { "Accept": "application/json" }, - }), - ); - assertEquals(await res3?.json(), { msg: "hello world" }); - } - - assert(await caches.delete(cacheName)); - assertFalse(await caches.has(cacheName)); -}); - -Deno.test(function cacheIllegalConstructor() { - assertThrows(() => new Cache(), TypeError, "Illegal constructor"); - // @ts-expect-error illegal constructor - assertThrows(() => new Cache("foo", "bar"), TypeError, "Illegal constructor"); -}); - -Deno.test(async function cachePutReaderLock() { - const cacheName = "cache-v1"; - const cache = await caches.open(cacheName); - - const response = new Response("consumed"); - - const promise = cache.put( - new Request("https://example.com/"), - response, - ); - - await assertRejects( - async () => { - await response.arrayBuffer(); - }, - TypeError, - "Body already consumed.", - ); - - await promise; -}); - -Deno.test(async function cachePutResourceLeak() { - const cacheName = "cache-v1"; - const cache = await caches.open(cacheName); - - const stream = new ReadableStream({ - start(controller) { - controller.error(new Error("leak")); - }, - }); - - await assertRejects( - async () => { - await cache.put( - new Request("https://example.com/leak"), - new Response(stream), - ); - }, - Error, - "leak", - ); -}); - -Deno.test(async function cachePutFailedBody() { - const cacheName = "cache-v1"; - const cache = await caches.open(cacheName); - - const request = new Request("https://example.com/failed-body"); - const stream = new ReadableStream({ - start(controller) { - controller.error(new Error("corrupt")); - }, - }); - - await assertRejects( - async () => { - await cache.put( - request, - new Response(stream), - ); - }, - Error, - "corrupt", - ); - - const response = await cache.match(request); - // if it fails to read the body, the cache should be empty - assertEquals(response, undefined); -}); - -Deno.test(async function cachePutOverwrite() { - const cacheName = "cache-v1"; - const cache = await caches.open(cacheName); - - const request = new Request("https://example.com/overwrite"); - const res1 = new Response("res1"); - const res2 = new Response("res2"); - - await cache.put(request, res1); - const res = await cache.match(request); - assertEquals(await res?.text(), "res1"); - - await cache.put(request, res2); - const res_ = await cache.match(request); - assertEquals(await res_?.text(), "res2"); -}); - -// Ensure that we can successfully put a response backed by a resource -Deno.test(async function cachePutResource() { - const tempFile = Deno.makeTempFileSync({ prefix: "deno-", suffix: ".txt" }); - Deno.writeTextFileSync(tempFile, "Contents".repeat(1024)); - - const file = Deno.openSync(tempFile); - - const cacheName = "cache-v1"; - const cache = await caches.open(cacheName); - - const request = new Request("https://example.com/file"); - await cache.put(request, new Response(file.readable)); - const res = await cache.match(request); - assertEquals(await res?.text(), "Contents".repeat(1024)); -}); diff --git a/cli/tests/unit/chmod_test.ts b/cli/tests/unit/chmod_test.ts deleted file mode 100644 index df3771bbc..000000000 --- a/cli/tests/unit/chmod_test.ts +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertRejects, - assertThrows, -} from "./test_util.ts"; - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - function chmodSyncSuccess() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const tempDir = Deno.makeTempDirSync(); - const filename = tempDir + "/test.txt"; - Deno.writeFileSync(filename, data, { mode: 0o666 }); - - Deno.chmodSync(filename, 0o777); - - const fileInfo = Deno.statSync(filename); - assert(fileInfo.mode); - assertEquals(fileInfo.mode & 0o777, 0o777); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - function chmodSyncUrl() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const tempDir = Deno.makeTempDirSync(); - const fileUrl = new URL(`file://${tempDir}/test.txt`); - Deno.writeFileSync(fileUrl, data, { mode: 0o666 }); - - Deno.chmodSync(fileUrl, 0o777); - - const fileInfo = Deno.statSync(fileUrl); - assert(fileInfo.mode); - assertEquals(fileInfo.mode & 0o777, 0o777); - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -// Check symlink when not on windows -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - function chmodSyncSymlinkSuccess() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const tempDir = Deno.makeTempDirSync(); - - const filename = tempDir + "/test.txt"; - Deno.writeFileSync(filename, data, { mode: 0o666 }); - const symlinkName = tempDir + "/test_symlink.txt"; - Deno.symlinkSync(filename, symlinkName); - - let symlinkInfo = Deno.lstatSync(symlinkName); - assert(symlinkInfo.mode); - const symlinkMode = symlinkInfo.mode & 0o777; // platform dependent - - Deno.chmodSync(symlinkName, 0o777); - - // Change actual file mode, not symlink - const fileInfo = Deno.statSync(filename); - assert(fileInfo.mode); - assertEquals(fileInfo.mode & 0o777, 0o777); - symlinkInfo = Deno.lstatSync(symlinkName); - assert(symlinkInfo.mode); - assertEquals(symlinkInfo.mode & 0o777, symlinkMode); - }, -); - -Deno.test({ permissions: { write: true } }, function chmodSyncFailure() { - const filename = "/badfile.txt"; - assertThrows( - () => { - Deno.chmodSync(filename, 0o777); - }, - Deno.errors.NotFound, - `chmod '${filename}'`, - ); -}); - -Deno.test({ permissions: { write: false } }, function chmodSyncPerm() { - assertThrows(() => { - Deno.chmodSync("/somefile.txt", 0o777); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - async function chmodSuccess() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const tempDir = Deno.makeTempDirSync(); - const filename = tempDir + "/test.txt"; - Deno.writeFileSync(filename, data, { mode: 0o666 }); - - await Deno.chmod(filename, 0o777); - - const fileInfo = Deno.statSync(filename); - assert(fileInfo.mode); - assertEquals(fileInfo.mode & 0o777, 0o777); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - async function chmodUrl() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const tempDir = Deno.makeTempDirSync(); - const fileUrl = new URL(`file://${tempDir}/test.txt`); - Deno.writeFileSync(fileUrl, data, { mode: 0o666 }); - - await Deno.chmod(fileUrl, 0o777); - - const fileInfo = Deno.statSync(fileUrl); - assert(fileInfo.mode); - assertEquals(fileInfo.mode & 0o777, 0o777); - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -// Check symlink when not on windows - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - async function chmodSymlinkSuccess() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const tempDir = Deno.makeTempDirSync(); - - const filename = tempDir + "/test.txt"; - Deno.writeFileSync(filename, data, { mode: 0o666 }); - const symlinkName = tempDir + "/test_symlink.txt"; - Deno.symlinkSync(filename, symlinkName); - - let symlinkInfo = Deno.lstatSync(symlinkName); - assert(symlinkInfo.mode); - const symlinkMode = symlinkInfo.mode & 0o777; // platform dependent - - await Deno.chmod(symlinkName, 0o777); - - // Just change actual file mode, not symlink - const fileInfo = Deno.statSync(filename); - assert(fileInfo.mode); - assertEquals(fileInfo.mode & 0o777, 0o777); - symlinkInfo = Deno.lstatSync(symlinkName); - assert(symlinkInfo.mode); - assertEquals(symlinkInfo.mode & 0o777, symlinkMode); - }, -); - -Deno.test({ permissions: { write: true } }, async function chmodFailure() { - const filename = "/badfile.txt"; - await assertRejects( - async () => { - await Deno.chmod(filename, 0o777); - }, - Deno.errors.NotFound, - `chmod '${filename}'`, - ); -}); - -Deno.test({ permissions: { write: false } }, async function chmodPerm() { - await assertRejects(async () => { - await Deno.chmod("/somefile.txt", 0o777); - }, Deno.errors.PermissionDenied); -}); diff --git a/cli/tests/unit/chown_test.ts b/cli/tests/unit/chown_test.ts deleted file mode 100644 index 033d4592d..000000000 --- a/cli/tests/unit/chown_test.ts +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals, assertRejects, assertThrows } from "./test_util.ts"; - -// chown on Windows is noop for now, so ignore its testing on Windows - -async function getUidAndGid(): Promise<{ uid: number; gid: number }> { - // get the user ID and group ID of the current process - const uidProc = await new Deno.Command("id", { - args: ["-u"], - }).output(); - const gidProc = await new Deno.Command("id", { - args: ["-g"], - }).output(); - - assertEquals(uidProc.code, 0); - assertEquals(gidProc.code, 0); - const uid = parseInt(new TextDecoder("utf-8").decode(uidProc.stdout)); - const gid = parseInt(new TextDecoder("utf-8").decode(gidProc.stdout)); - - return { uid, gid }; -} - -Deno.test( - { ignore: Deno.build.os == "windows", permissions: { write: false } }, - async function chownNoWritePermission() { - const filePath = "chown_test_file.txt"; - await assertRejects(async () => { - await Deno.chown(filePath, 1000, 1000); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { - permissions: { run: true, write: true }, - ignore: Deno.build.os == "windows", - }, - async function chownSyncFileNotExist() { - const { uid, gid } = await getUidAndGid(); - const filePath = Deno.makeTempDirSync() + "/chown_test_file.txt"; - - assertThrows( - () => { - Deno.chownSync(filePath, uid, gid); - }, - Deno.errors.NotFound, - `chown '${filePath}'`, - ); - }, -); - -Deno.test( - { - permissions: { run: true, write: true }, - ignore: Deno.build.os == "windows", - }, - async function chownFileNotExist() { - const { uid, gid } = await getUidAndGid(); - const filePath = (await Deno.makeTempDir()) + "/chown_test_file.txt"; - - await assertRejects( - async () => { - await Deno.chown(filePath, uid, gid); - }, - Deno.errors.NotFound, - `chown '${filePath}'`, - ); - }, -); - -Deno.test( - { permissions: { write: true }, ignore: Deno.build.os == "windows" }, - function chownSyncPermissionDenied() { - const dirPath = Deno.makeTempDirSync(); - const filePath = dirPath + "/chown_test_file.txt"; - Deno.writeTextFileSync(filePath, "Hello"); - - assertThrows(() => { - // try changing the file's owner to root - Deno.chownSync(filePath, 0, 0); - }, Deno.errors.PermissionDenied); - Deno.removeSync(dirPath, { recursive: true }); - }, -); - -Deno.test( - { permissions: { write: true }, ignore: Deno.build.os == "windows" }, - async function chownPermissionDenied() { - const dirPath = await Deno.makeTempDir(); - const filePath = dirPath + "/chown_test_file.txt"; - await Deno.writeTextFile(filePath, "Hello"); - - await assertRejects(async () => { - // try changing the file's owner to root - await Deno.chown(filePath, 0, 0); - }, Deno.errors.PermissionDenied); - await Deno.remove(dirPath, { recursive: true }); - }, -); - -Deno.test( - { - permissions: { run: true, write: true }, - ignore: Deno.build.os == "windows", - }, - async function chownSyncSucceed() { - // TODO(bartlomieju): when a file's owner is actually being changed, - // chown only succeeds if run under privileged user (root) - // The test script has no such privilege, so need to find a better way to test this case - const { uid, gid } = await getUidAndGid(); - - const dirPath = Deno.makeTempDirSync(); - const filePath = dirPath + "/chown_test_file.txt"; - Deno.writeTextFileSync(filePath, "Hello"); - - // the test script creates this file with the same uid and gid, - // here chown is a noop so it succeeds under non-privileged user - Deno.chownSync(filePath, uid, gid); - - Deno.removeSync(dirPath, { recursive: true }); - }, -); - -Deno.test( - { - permissions: { run: true, write: true }, - ignore: Deno.build.os == "windows", - }, - async function chownSyncWithUrl() { - const { uid, gid } = await getUidAndGid(); - const dirPath = Deno.makeTempDirSync(); - const fileUrl = new URL(`file://${dirPath}/chown_test_file.txt`); - Deno.writeTextFileSync(fileUrl, "Hello"); - Deno.chownSync(fileUrl, uid, gid); - Deno.removeSync(dirPath, { recursive: true }); - }, -); - -Deno.test( - { - permissions: { run: true, write: true }, - ignore: Deno.build.os == "windows", - }, - async function chownSucceed() { - const { uid, gid } = await getUidAndGid(); - const dirPath = await Deno.makeTempDir(); - const filePath = dirPath + "/chown_test_file.txt"; - await Deno.writeTextFile(filePath, "Hello"); - await Deno.chown(filePath, uid, gid); - Deno.removeSync(dirPath, { recursive: true }); - }, -); - -Deno.test( - { - permissions: { run: true, write: true }, - ignore: Deno.build.os == "windows", - }, - async function chownUidOnly() { - const { uid } = await getUidAndGid(); - const dirPath = await Deno.makeTempDir(); - const filePath = dirPath + "/chown_test_file.txt"; - await Deno.writeTextFile(filePath, "Foo"); - await Deno.chown(filePath, uid, null); - Deno.removeSync(dirPath, { recursive: true }); - }, -); - -Deno.test( - { - permissions: { run: true, write: true }, - ignore: Deno.build.os == "windows", - }, - async function chownWithUrl() { - // TODO(bartlomieju): same as chownSyncSucceed - const { uid, gid } = await getUidAndGid(); - - const enc = new TextEncoder(); - const dirPath = await Deno.makeTempDir(); - const fileUrl = new URL(`file://${dirPath}/chown_test_file.txt`); - const fileData = enc.encode("Hello"); - await Deno.writeFile(fileUrl, fileData); - - // the test script creates this file with the same uid and gid, - // here chown is a noop so it succeeds under non-privileged user - await Deno.chown(fileUrl, uid, gid); - - Deno.removeSync(dirPath, { recursive: true }); - }, -); diff --git a/cli/tests/unit/command_test.ts b/cli/tests/unit/command_test.ts deleted file mode 100644 index cbb1c4921..000000000 --- a/cli/tests/unit/command_test.ts +++ /dev/null @@ -1,967 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { - assert, - assertEquals, - assertRejects, - assertStringIncludes, - assertThrows, -} from "./test_util.ts"; - -Deno.test( - { permissions: { write: true, run: true, read: true } }, - async function commandWithCwdIsAsync() { - const enc = new TextEncoder(); - const cwd = await Deno.makeTempDir({ prefix: "deno_command_test" }); - - const exitCodeFile = "deno_was_here"; - const programFile = "poll_exit.ts"; - const program = ` -async function tryExit() { - try { - const code = parseInt(await Deno.readTextFile("${exitCodeFile}")); - Deno.exit(code); - } catch { - // Retry if we got here before deno wrote the file. - setTimeout(tryExit, 0.01); - } -} - -tryExit(); -`; - - Deno.writeFileSync(`${cwd}/${programFile}`, enc.encode(program)); - - const command = new Deno.Command(Deno.execPath(), { - cwd, - args: ["run", "--allow-read", programFile], - stdout: "inherit", - stderr: "inherit", - }); - const child = command.spawn(); - - // Write the expected exit code *after* starting deno. - // This is how we verify that `Child` is actually asynchronous. - const code = 84; - Deno.writeFileSync(`${cwd}/${exitCodeFile}`, enc.encode(`${code}`)); - - const status = await child.status; - await Deno.remove(cwd, { recursive: true }); - assertEquals(status.success, false); - assertEquals(status.code, code); - assertEquals(status.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandStdinPiped() { - const command = new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "if (new TextDecoder().decode(await Deno.readAll(Deno.stdin)) !== 'hello') throw new Error('Expected \\'hello\\'')", - ], - stdin: "piped", - stdout: "null", - stderr: "null", - }); - const child = command.spawn(); - - assertThrows(() => child.stdout, TypeError, "stdout is not piped"); - assertThrows(() => child.stderr, TypeError, "stderr is not piped"); - - const msg = new TextEncoder().encode("hello"); - const writer = child.stdin.getWriter(); - await writer.write(msg); - writer.releaseLock(); - - await child.stdin.close(); - const status = await child.status; - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandStdinPiped() { - const command = new Deno.Command(Deno.execPath(), { - args: ["info"], - stdout: "null", - stderr: "null", - }); - const child = command.spawn(); - - assertThrows(() => child.stdin, TypeError, "stdin is not piped"); - assertThrows(() => child.stdout, TypeError, "stdout is not piped"); - assertThrows(() => child.stderr, TypeError, "stderr is not piped"); - - await child.status; - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandStdoutPiped() { - const command = new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "await Deno.stdout.write(new TextEncoder().encode('hello'))", - ], - stderr: "null", - stdout: "piped", - }); - const child = command.spawn(); - - assertThrows(() => child.stdin, TypeError, "stdin is not piped"); - assertThrows(() => child.stderr, TypeError, "stderr is not piped"); - - const readable = child.stdout.pipeThrough(new TextDecoderStream()); - const reader = readable.getReader(); - const res = await reader.read(); - assert(!res.done); - assertEquals(res.value, "hello"); - - const resEnd = await reader.read(); - assert(resEnd.done); - assertEquals(resEnd.value, undefined); - reader.releaseLock(); - - const status = await child.status; - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandStderrPiped() { - const command = new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "await Deno.stderr.write(new TextEncoder().encode('hello'))", - ], - stdout: "null", - stderr: "piped", - }); - const child = command.spawn(); - - assertThrows(() => child.stdin, TypeError, "stdin is not piped"); - assertThrows(() => child.stdout, TypeError, "stdout is not piped"); - - const readable = child.stderr.pipeThrough(new TextDecoderStream()); - const reader = readable.getReader(); - const res = await reader.read(); - assert(!res.done); - assertEquals(res.value, "hello"); - - const resEnd = await reader.read(); - assert(resEnd.done); - assertEquals(resEnd.value, undefined); - reader.releaseLock(); - - const status = await child.status; - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, write: true, read: true } }, - async function commandRedirectStdoutStderr() { - const tempDir = await Deno.makeTempDir(); - const fileName = tempDir + "/redirected_stdio.txt"; - const file = await Deno.open(fileName, { - create: true, - write: true, - }); - - const command = new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "Deno.stderr.write(new TextEncoder().encode('error\\n')); Deno.stdout.write(new TextEncoder().encode('output\\n'));", - ], - stdout: "piped", - stderr: "piped", - }); - const child = command.spawn(); - await child.stdout.pipeTo(file.writable, { - preventClose: true, - }); - await child.stderr.pipeTo(file.writable); - await child.status; - - const fileContents = await Deno.readFile(fileName); - const decoder = new TextDecoder(); - const text = decoder.decode(fileContents); - - assertStringIncludes(text, "error"); - assertStringIncludes(text, "output"); - }, -); - -Deno.test( - { permissions: { run: true, write: true, read: true } }, - async function commandRedirectStdin() { - const tempDir = await Deno.makeTempDir(); - const fileName = tempDir + "/redirected_stdio.txt"; - await Deno.writeTextFile(fileName, "hello"); - const file = await Deno.open(fileName); - - const command = new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "if (new TextDecoder().decode(await Deno.readAll(Deno.stdin)) !== 'hello') throw new Error('Expected \\'hello\\'')", - ], - stdin: "piped", - stdout: "null", - stderr: "null", - }); - const child = command.spawn(); - await file.readable.pipeTo(child.stdin, { - preventClose: true, - }); - - await child.stdin.close(); - const status = await child.status; - assertEquals(status.code, 0); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandKillSuccess() { - const command = new Deno.Command(Deno.execPath(), { - args: ["eval", "setTimeout(() => {}, 10000)"], - stdout: "null", - stderr: "null", - }); - const child = command.spawn(); - - child.kill("SIGKILL"); - const status = await child.status; - - assertEquals(status.success, false); - if (Deno.build.os === "windows") { - assertEquals(status.code, 1); - assertEquals(status.signal, null); - } else { - assertEquals(status.code, 137); - assertEquals(status.signal, "SIGKILL"); - } - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - // deno lint bug, see https://github.com/denoland/deno_lint/issues/1206 - // deno-lint-ignore require-await - async function childProcessExplicitResourceManagement() { - let dead = undefined; - { - const command = new Deno.Command(Deno.execPath(), { - args: ["eval", "setTimeout(() => {}, 10000)"], - stdout: "null", - stderr: "null", - }); - await using child = command.spawn(); - child.status.then(({ signal }) => { - dead = signal; - }); - } - - if (Deno.build.os == "windows") { - assertEquals(dead, null); - } else { - assertEquals(dead, "SIGTERM"); - } - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function childProcessExplicitResourceManagementManualClose() { - const command = new Deno.Command(Deno.execPath(), { - args: ["eval", "setTimeout(() => {}, 10000)"], - stdout: "null", - stderr: "null", - }); - await using child = command.spawn(); - child.kill("SIGTERM"); - await child.status; - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandKillFailed() { - const command = new Deno.Command(Deno.execPath(), { - args: ["eval", "setTimeout(() => {}, 5000)"], - stdout: "null", - stderr: "null", - }); - const child = command.spawn(); - - assertThrows(() => { - // @ts-expect-error testing runtime error of bad signal - child.kill("foobar"); - }, TypeError); - - await child.status; - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandKillOptional() { - const command = new Deno.Command(Deno.execPath(), { - args: ["eval", "setTimeout(() => {}, 10000)"], - stdout: "null", - stderr: "null", - }); - const child = command.spawn(); - - child.kill(); - const status = await child.status; - - assertEquals(status.success, false); - if (Deno.build.os === "windows") { - assertEquals(status.code, 1); - assertEquals(status.signal, null); - } else { - assertEquals(status.code, 143); - assertEquals(status.signal, "SIGTERM"); - } - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandAbort() { - const ac = new AbortController(); - const command = new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "setTimeout(console.log, 1e8)", - ], - signal: ac.signal, - stdout: "null", - stderr: "null", - }); - const child = command.spawn(); - queueMicrotask(() => ac.abort()); - const status = await child.status; - assertEquals(status.success, false); - if (Deno.build.os === "windows") { - assertEquals(status.code, 1); - assertEquals(status.signal, null); - } else { - assertEquals(status.success, false); - assertEquals(status.code, 143); - } - }, -); - -Deno.test( - { permissions: { read: true, run: false } }, - async function commandPermissions() { - await assertRejects(async () => { - await new Deno.Command(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }).output(); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, run: false } }, - function commandSyncPermissions() { - assertThrows(() => { - new Deno.Command(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }).outputSync(); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandSuccess() { - const output = await new Deno.Command(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }).output(); - - assertEquals(output.success, true); - assertEquals(output.code, 0); - assertEquals(output.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function commandSyncSuccess() { - const output = new Deno.Command(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }).outputSync(); - - assertEquals(output.success, true); - assertEquals(output.code, 0); - assertEquals(output.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandUrl() { - const output = await new Deno.Command( - new URL(`file:///${Deno.execPath()}`), - { - args: ["eval", "console.log('hello world')"], - }, - ).output(); - - assertEquals(new TextDecoder().decode(output.stdout), "hello world\n"); - - assertEquals(output.success, true); - assertEquals(output.code, 0); - assertEquals(output.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function commandSyncUrl() { - const output = new Deno.Command( - new URL(`file:///${Deno.execPath()}`), - { - args: ["eval", "console.log('hello world')"], - }, - ).outputSync(); - - assertEquals(new TextDecoder().decode(output.stdout), "hello world\n"); - - assertEquals(output.success, true); - assertEquals(output.code, 0); - assertEquals(output.signal, null); - }, -); - -Deno.test({ permissions: { run: true } }, function commandNotFound() { - assertThrows( - () => new Deno.Command("this file hopefully doesn't exist").output(), - Deno.errors.NotFound, - ); -}); - -Deno.test({ permissions: { run: true } }, function commandSyncNotFound() { - assertThrows( - () => new Deno.Command("this file hopefully doesn't exist").outputSync(), - Deno.errors.NotFound, - ); -}); - -Deno.test({ permissions: { run: true, read: true } }, function cwdNotFound() { - assertThrows( - () => - new Deno.Command(Deno.execPath(), { - cwd: Deno.cwd() + "/non-existent-directory", - }).output(), - Deno.errors.NotFound, - "No such cwd", - ); -}); - -Deno.test( - { permissions: { run: true, read: true } }, - function cwdNotDirectory() { - assertThrows( - () => - new Deno.Command(Deno.execPath(), { - cwd: Deno.execPath(), - }).output(), - Deno.errors.NotFound, - "cwd is not a directory", - ); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandFailedWithCode() { - const output = await new Deno.Command(Deno.execPath(), { - args: ["eval", "Deno.exit(41 + 1)"], - }).output(); - assertEquals(output.success, false); - assertEquals(output.code, 42); - assertEquals(output.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function commandSyncFailedWithCode() { - const output = new Deno.Command(Deno.execPath(), { - args: ["eval", "Deno.exit(41 + 1)"], - }).outputSync(); - assertEquals(output.success, false); - assertEquals(output.code, 42); - assertEquals(output.signal, null); - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - }, - async function commandFailedWithSignal() { - const output = await new Deno.Command(Deno.execPath(), { - args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"], - }).output(); - assertEquals(output.success, false); - if (Deno.build.os === "windows") { - assertEquals(output.code, 1); - assertEquals(output.signal, null); - } else { - assertEquals(output.code, 128 + 9); - assertEquals(output.signal, "SIGKILL"); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - }, - function commandSyncFailedWithSignal() { - const output = new Deno.Command(Deno.execPath(), { - args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"], - }).outputSync(); - assertEquals(output.success, false); - if (Deno.build.os === "windows") { - assertEquals(output.code, 1); - assertEquals(output.signal, null); - } else { - assertEquals(output.code, 128 + 9); - assertEquals(output.signal, "SIGKILL"); - } - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandOutput() { - const { stdout } = await new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "await Deno.stdout.write(new TextEncoder().encode('hello'))", - ], - }).output(); - - const s = new TextDecoder().decode(stdout); - assertEquals(s, "hello"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function commandSyncOutput() { - const { stdout } = new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "await Deno.stdout.write(new TextEncoder().encode('hello'))", - ], - }).outputSync(); - - const s = new TextDecoder().decode(stdout); - assertEquals(s, "hello"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandStderrOutput() { - const { stderr } = await new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "await Deno.stderr.write(new TextEncoder().encode('error'))", - ], - }).output(); - - const s = new TextDecoder().decode(stderr); - assertEquals(s, "error"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function commandSyncStderrOutput() { - const { stderr } = new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "await Deno.stderr.write(new TextEncoder().encode('error'))", - ], - }).outputSync(); - - const s = new TextDecoder().decode(stderr); - assertEquals(s, "error"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandEnv() { - const { stdout } = await new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", - ], - env: { - FOO: "0123", - BAR: "4567", - }, - }).output(); - const s = new TextDecoder().decode(stdout); - assertEquals(s, "01234567"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function commandSyncEnv() { - const { stdout } = new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", - ], - env: { - FOO: "0123", - BAR: "4567", - }, - }).outputSync(); - const s = new TextDecoder().decode(stdout); - assertEquals(s, "01234567"); - }, -); - -Deno.test( - { permissions: { run: true, read: true, env: true } }, - async function commandClearEnv() { - const { stdout } = await new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "-p", - "JSON.stringify(Deno.env.toObject())", - ], - clearEnv: true, - env: { - FOO: "23147", - }, - }).output(); - - const obj = JSON.parse(new TextDecoder().decode(stdout)); - - // can't check for object equality because the OS may set additional env - // vars for processes, so we check if PATH isn't present as that is a common - // env var across OS's and isn't set for processes. - assertEquals(obj.FOO, "23147"); - assert(!("PATH" in obj)); - }, -); - -Deno.test( - { permissions: { run: true, read: true, env: true } }, - function commandSyncClearEnv() { - const { stdout } = new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "-p", - "JSON.stringify(Deno.env.toObject())", - ], - clearEnv: true, - env: { - FOO: "23147", - }, - }).outputSync(); - - const obj = JSON.parse(new TextDecoder().decode(stdout)); - - // can't check for object equality because the OS may set additional env - // vars for processes, so we check if PATH isn't present as that is a common - // env var across OS's and isn't set for processes. - assertEquals(obj.FOO, "23147"); - assert(!("PATH" in obj)); - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - async function commandUid() { - const { stdout } = await new Deno.Command("id", { - args: ["-u"], - }).output(); - - const currentUid = new TextDecoder().decode(stdout); - - if (currentUid !== "0") { - await assertRejects(async () => { - await new Deno.Command("echo", { - args: ["fhqwhgads"], - uid: 0, - }).output(); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - function commandSyncUid() { - const { stdout } = new Deno.Command("id", { - args: ["-u"], - }).outputSync(); - - const currentUid = new TextDecoder().decode(stdout); - - if (currentUid !== "0") { - assertThrows(() => { - new Deno.Command("echo", { - args: ["fhqwhgads"], - uid: 0, - }).outputSync(); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - async function commandGid() { - const { stdout } = await new Deno.Command("id", { - args: ["-g"], - }).output(); - - const currentGid = new TextDecoder().decode(stdout); - - if (currentGid !== "0") { - await assertRejects(async () => { - await new Deno.Command("echo", { - args: ["fhqwhgads"], - gid: 0, - }).output(); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - function commandSyncGid() { - const { stdout } = new Deno.Command("id", { - args: ["-g"], - }).outputSync(); - - const currentGid = new TextDecoder().decode(stdout); - - if (currentGid !== "0") { - assertThrows(() => { - new Deno.Command("echo", { - args: ["fhqwhgads"], - gid: 0, - }).outputSync(); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test(function commandStdinPipedFails() { - assertThrows( - () => - new Deno.Command("id", { - stdin: "piped", - }).output(), - TypeError, - "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", - ); -}); - -Deno.test(function spawnSyncStdinPipedFails() { - assertThrows( - () => - new Deno.Command("id", { - stdin: "piped", - }).outputSync(), - TypeError, - "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", - ); -}); - -Deno.test( - // FIXME(bartlomieju): this test is very flaky on CI, fix it - { - ignore: true, - permissions: { write: true, run: true, read: true }, - }, - async function commandChildUnref() { - const enc = new TextEncoder(); - const cwd = await Deno.makeTempDir({ prefix: "deno_command_test" }); - - const programFile = "unref.ts"; - const program = ` -const command = await new Deno.Command(Deno.execPath(), { - cwd: Deno.args[0], - stdout: "piped", - args: ["run", "-A", "--unstable", Deno.args[1]], -}); -const child = command.spawn(); -const readable = child.stdout.pipeThrough(new TextDecoderStream()); -const reader = readable.getReader(); -// set up an interval that will end after reading a few messages from stdout, -// to verify that stdio streams are properly unrefed -let count = 0; -let interval; -interval = setInterval(async () => { - count += 1; - if (count > 10) { - clearInterval(interval); - console.log("cleared interval"); - } - const res = await reader.read(); - if (res.done) { - throw new Error("stream shouldn't be done"); - } - if (res.value.trim() != "hello from interval") { - throw new Error("invalid message received"); - } -}, 120); -console.log("spawned pid", child.pid); -child.unref(); -`; - - const childProgramFile = "unref_child.ts"; - const childProgram = ` -setInterval(() => { - console.log("hello from interval"); -}, 100); -`; - Deno.writeFileSync(`${cwd}/${programFile}`, enc.encode(program)); - Deno.writeFileSync(`${cwd}/${childProgramFile}`, enc.encode(childProgram)); - // In this subprocess we are spawning another subprocess which has - // an infinite interval set. Following call would never resolve unless - // child process gets unrefed. - const { success, stdout, stderr } = await new Deno.Command( - Deno.execPath(), - { - cwd, - args: ["run", "-A", "--unstable", programFile, cwd, childProgramFile], - }, - ).output(); - - assert(success); - const stdoutText = new TextDecoder().decode(stdout); - const stderrText = new TextDecoder().decode(stderr); - assert(stderrText.length == 0); - const [line1, line2] = stdoutText.split("\n"); - const pidStr = line1.split(" ").at(-1); - assert(pidStr); - assertEquals(line2, "cleared interval"); - const pid = Number.parseInt(pidStr, 10); - await Deno.remove(cwd, { recursive: true }); - // Child process should have been killed when parent process exits. - assertThrows(() => { - Deno.kill(pid, "SIGTERM"); - }, Deno.errors.NotFound); - }, -); - -Deno.test( - { ignore: Deno.build.os !== "windows" }, - async function commandWindowsRawArguments() { - let { success, stdout } = await new Deno.Command("cmd", { - args: ["/d", "/s", "/c", '"deno ^"--version^""'], - windowsRawArguments: true, - }).output(); - assert(success); - let stdoutText = new TextDecoder().decode(stdout); - assertStringIncludes(stdoutText, "deno"); - assertStringIncludes(stdoutText, "v8"); - assertStringIncludes(stdoutText, "typescript"); - - ({ success, stdout } = new Deno.Command("cmd", { - args: ["/d", "/s", "/c", '"deno ^"--version^""'], - windowsRawArguments: true, - }).outputSync()); - assert(success); - stdoutText = new TextDecoder().decode(stdout); - assertStringIncludes(stdoutText, "deno"); - assertStringIncludes(stdoutText, "v8"); - assertStringIncludes(stdoutText, "typescript"); - }, -); - -Deno.test( - { permissions: { read: true, run: true } }, - async function commandWithPrototypePollution() { - const originalThen = Promise.prototype.then; - const originalSymbolIterator = Array.prototype[Symbol.iterator]; - try { - Promise.prototype.then = Array.prototype[Symbol.iterator] = () => { - throw new Error(); - }; - await new Deno.Command(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }).output(); - } finally { - Promise.prototype.then = originalThen; - Array.prototype[Symbol.iterator] = originalSymbolIterator; - } - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function commandKillAfterStatus() { - const command = new Deno.Command(Deno.execPath(), { - args: ["help"], - stdout: "null", - stderr: "null", - }); - const child = command.spawn(); - await child.status; - assertThrows( - () => child.kill(), - TypeError, - "Child process has already terminated.", - ); - }, -); - -Deno.test( - "process that fails to spawn, prints its name in error", - async () => { - assertThrows( - () => new Deno.Command("doesntexist").outputSync(), - Error, - "Failed to spawn 'doesntexist'", - ); - await assertRejects( - async () => await new Deno.Command("doesntexist").output(), - Error, - "Failed to spawn 'doesntexist'", - ); - }, -); diff --git a/cli/tests/unit/console_test.ts b/cli/tests/unit/console_test.ts deleted file mode 100644 index 2f24b2c4e..000000000 --- a/cli/tests/unit/console_test.ts +++ /dev/null @@ -1,2411 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -// TODO(ry) The unit test functions in this module are too coarse. They should -// be broken up into smaller bits. - -// TODO(ry) These tests currently strip all the ANSI colors out. We don't have a -// good way to control whether we produce color output or not since -// std/fmt/colors auto determines whether to put colors in or not. We need -// better infrastructure here so we can properly test the colors. - -import { - assert, - assertEquals, - assertStringIncludes, - assertThrows, -} from "./test_util.ts"; -import { stripColor } from "@test_util/std/fmt/colors.ts"; - -const customInspect = Symbol.for("Deno.customInspect"); -const { - Console, - cssToAnsi: cssToAnsi_, - inspectArgs, - parseCss: parseCss_, - parseCssColor: parseCssColor_, - // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol -} = Deno[Deno.internal]; - -function stringify(...args: unknown[]): string { - return stripColor(inspectArgs(args).replace(/\n$/, "")); -} - -interface Css { - backgroundColor: [number, number, number] | string | null; - color: [number, number, number] | string | null; - fontWeight: string | null; - fontStyle: string | null; - textDecorationColor: [number, number, number] | null; - textDecorationLine: string[]; -} - -const DEFAULT_CSS: Css = { - backgroundColor: null, - color: null, - fontWeight: null, - fontStyle: null, - textDecorationColor: null, - textDecorationLine: [], -}; - -function parseCss(cssString: string): Css { - return parseCss_(cssString); -} - -function parseCssColor(colorString: string): [number, number, number] | null { - return parseCssColor_(colorString); -} - -/** ANSI-fy the CSS, replace "\x1b" with "_". */ -function cssToAnsiEsc(css: Css, prevCss: Css | null = null): string { - return cssToAnsi_(css, prevCss).replaceAll("\x1b", "_"); -} - -// test cases from web-platform-tests -// via https://github.com/web-platform-tests/wpt/blob/master/console/console-is-a-namespace.any.js -Deno.test(function consoleShouldBeANamespace() { - const prototype1 = Object.getPrototypeOf(console); - const prototype2 = Object.getPrototypeOf(prototype1); - - assertEquals(Object.getOwnPropertyNames(prototype1).length, 0); - assertEquals(prototype2, Object.prototype); -}); - -Deno.test(function consoleHasRightInstance() { - assert(console instanceof Console); - assertEquals({} instanceof Console, false); -}); - -Deno.test(function consoleTestAssertShouldNotThrowError() { - mockConsole((console) => { - console.assert(true); - let hasThrown = undefined; - try { - console.assert(false); - hasThrown = false; - } catch { - hasThrown = true; - } - assertEquals(hasThrown, false); - }); -}); - -Deno.test(function consoleTestStringifyComplexObjects() { - assertEquals(stringify("foo"), "foo"); - assertEquals(stringify(["foo", "bar"]), `[ "foo", "bar" ]`); - assertEquals(stringify({ foo: "bar" }), `{ foo: "bar" }`); -}); - -Deno.test( - function consoleTestStringifyComplexObjectsWithEscapedSequences() { - assertEquals( - stringify( - ["foo\b", "foo\f", "foo\n", "foo\r", "foo\t", "foo\v", "foo\0"], - ), - `[ - "foo\\b", "foo\\f", - "foo\\n", "foo\\r", - "foo\\t", "foo\\v", - "foo\\x00" -]`, - ); - assertEquals( - stringify( - [ - Symbol(), - Symbol(""), - Symbol("foo\b"), - Symbol("foo\f"), - Symbol("foo\n"), - Symbol("foo\r"), - Symbol("foo\t"), - Symbol("foo\v"), - Symbol("foo\0"), - ], - ), - `[ - Symbol(), - Symbol(""), - Symbol("foo\\b"), - Symbol("foo\\f"), - Symbol("foo\\n"), - Symbol("foo\\r"), - Symbol("foo\\t"), - Symbol("foo\\v"), - Symbol("foo\\x00") -]`, - ); - assertEquals( - stringify( - { "foo\b": "bar\n", "bar\r": "baz\t", "qux\0": "qux\0" }, - ), - `{ "foo\\b": "bar\\n", "bar\\r": "baz\\t", "qux\\x00": "qux\\x00" }`, - ); - assertEquals( - stringify( - { - [Symbol("foo\b")]: `Symbol("foo\n")`, - [Symbol("bar\n")]: `Symbol("bar\n")`, - [Symbol("bar\r")]: `Symbol("bar\r")`, - [Symbol("baz\t")]: `Symbol("baz\t")`, - [Symbol("qux\0")]: `Symbol("qux\0")`, - }, - ), - `{ - [Symbol("foo\\b")]: 'Symbol("foo\\n")', - [Symbol("bar\\n")]: 'Symbol("bar\\n")', - [Symbol("bar\\r")]: 'Symbol("bar\\r")', - [Symbol("baz\\t")]: 'Symbol("baz\\t")', - [Symbol("qux\\x00")]: 'Symbol("qux\\x00")' -}`, - ); - assertEquals( - stringify(new Set(["foo\n", "foo\r", "foo\0"])), - `Set(3) { "foo\\n", "foo\\r", "foo\\x00" }`, - ); - }, -); - -Deno.test(function consoleTestStringifyQuotes() { - assertEquals(stringify(["\\"]), `[ "\\\\" ]`); - assertEquals(stringify(['\\,"']), `[ '\\\\,"' ]`); - assertEquals(stringify([`\\,",'`]), `[ \`\\\\,",'\` ]`); - assertEquals(stringify(["\\,\",',`"]), `[ "\\\\,\\",',\`" ]`); -}); - -Deno.test(function consoleTestStringifyLongStrings() { - const veryLongString = "a".repeat(200); - // If we stringify an object containing the long string, it gets abbreviated. - let actual = stringify({ veryLongString }); - assert(actual.includes("...")); - assert(actual.length < 200); - // However if we stringify the string itself, we get it exactly. - actual = stringify(veryLongString); - assertEquals(actual, veryLongString); -}); - -Deno.test(function consoleTestStringifyCircular() { - class Base { - a = 1; - m1() {} - } - - class Extended extends Base { - b = 2; - m2() {} - } - - // deno-lint-ignore no-explicit-any - const nestedObj: any = { - num: 1, - bool: true, - str: "a", - method() {}, - async asyncMethod() {}, - *generatorMethod() {}, - un: undefined, - nu: null, - arrowFunc: () => {}, - extendedClass: new Extended(), - nFunc: new Function(), - extendedCstr: Extended, - }; - - const circularObj = { - num: 2, - bool: false, - str: "b", - method() {}, - un: undefined, - nu: null, - nested: nestedObj, - emptyObj: {}, - arr: [1, "s", false, null, nestedObj], - baseClass: new Base(), - }; - - nestedObj.o = circularObj; - const nestedObjExpected = `<ref *1> { - num: 1, - bool: true, - str: "a", - method: [Function: method], - asyncMethod: [AsyncFunction: asyncMethod], - generatorMethod: [GeneratorFunction: generatorMethod], - un: undefined, - nu: null, - arrowFunc: [Function: arrowFunc], - extendedClass: Extended { a: 1, b: 2 }, - nFunc: [Function: anonymous], - extendedCstr: [class Extended extends Base], - o: { - num: 2, - bool: false, - str: "b", - method: [Function: method], - un: undefined, - nu: null, - nested: [Circular *1], - emptyObj: {}, - arr: [ 1, "s", false, null, [Circular *1] ], - baseClass: Base { a: 1 } - } -}`; - - assertEquals(stringify(1), "1"); - assertEquals(stringify(-0), "-0"); - assertEquals(stringify(1n), "1n"); - assertEquals(stringify("s"), "s"); - assertEquals(stringify(false), "false"); - assertEquals(stringify(new Number(1)), "[Number: 1]"); - assertEquals(stringify(new Number(-0)), "[Number: -0]"); - assertEquals(stringify(Object(1n)), "[BigInt: 1n]"); - assertEquals(stringify(new Boolean(true)), "[Boolean: true]"); - assertEquals(stringify(new String("deno")), `[String: "deno"]`); - assertEquals(stringify(/[0-9]*/), "/[0-9]*/"); - assertEquals( - stringify(new Date("2018-12-10T02:26:59.002Z")), - "2018-12-10T02:26:59.002Z", - ); - assertEquals(stringify(new Set([1, 2, 3])), "Set(3) { 1, 2, 3 }"); - assertEquals( - stringify(new Set([1, 2, 3]).values()), - "[Set Iterator] { 1, 2, 3 }", - ); - assertEquals( - stringify(new Set([1, 2, 3]).entries()), - "[Set Entries] { [ 1, 1 ], [ 2, 2 ], [ 3, 3 ] }", - ); - assertEquals( - stringify( - new Map([ - [1, "one"], - [2, "two"], - ]), - ), - `Map(2) { 1 => "one", 2 => "two" }`, - ); - assertEquals( - stringify(new Map([[1, "one"], [2, "two"]]).values()), - `[Map Iterator] { "one", "two" }`, - ); - assertEquals( - stringify(new Map([[1, "one"], [2, "two"]]).entries()), - `[Map Entries] { [ 1, "one" ], [ 2, "two" ] }`, - ); - assertEquals(stringify(new WeakSet()), "WeakSet { <items unknown> }"); - assertEquals(stringify(new WeakMap()), "WeakMap { <items unknown> }"); - assertEquals(stringify(Symbol(1)), `Symbol("1")`); - assertEquals(stringify(Object(Symbol(1))), `[Symbol: Symbol("1")]`); - assertEquals(stringify(null), "null"); - assertEquals(stringify(undefined), "undefined"); - assertEquals(stringify(new Extended()), "Extended { a: 1, b: 2 }"); - assertEquals( - stringify(function f() {}), - "[Function: f]", - ); - assertEquals( - stringify(async function af() {}), - "[AsyncFunction: af]", - ); - assertEquals( - stringify(function* gf() {}), - "[GeneratorFunction: gf]", - ); - assertEquals( - stringify(async function* agf() {}), - "[AsyncGeneratorFunction: agf]", - ); - assertEquals( - stringify(new Uint8Array([1, 2, 3])), - "Uint8Array(3) [ 1, 2, 3 ]", - ); - assertEquals(stringify(Uint8Array.prototype), "TypedArray {}"); - assertEquals( - stringify({ a: { b: { c: { d: new Set([1]) } } } }), - `{ - a: { - b: { c: { d: Set(1) { 1 } } } - } -}`, - ); - assertEquals(stringify(nestedObj), nestedObjExpected); - assertEquals( - stringify(JSON), - "Object [JSON] {}", - ); - assertEquals( - stringify(new Console(() => {})), - `Object [console] { - log: [Function: log], - debug: [Function: debug], - info: [Function: info], - dir: [Function: dir], - dirxml: [Function: dir], - warn: [Function: warn], - error: [Function: error], - assert: [Function: assert], - count: [Function: count], - countReset: [Function: countReset], - table: [Function: table], - time: [Function: time], - timeLog: [Function: timeLog], - timeEnd: [Function: timeEnd], - group: [Function: group], - groupCollapsed: [Function: group], - groupEnd: [Function: groupEnd], - clear: [Function: clear], - trace: [Function: trace], - profile: [Function: profile], - profileEnd: [Function: profileEnd], - timeStamp: [Function: timeStamp], - indentLevel: 0, - [Symbol(isConsoleInstance)]: true -}`, - ); - assertEquals( - stringify({ str: 1, [Symbol.for("sym")]: 2, [Symbol.toStringTag]: "TAG" }), - `Object [TAG] { - str: 1, - [Symbol(sym)]: 2, - [Symbol(Symbol.toStringTag)]: "TAG" -}`, - ); - // test inspect is working the same - assertEquals(stripColor(Deno.inspect(nestedObj)), nestedObjExpected); -}); - -Deno.test(function consoleTestStringifyMultipleCircular() { - const y = { a: { b: {} }, foo: { bar: {} } }; - y.a.b = y.a; - y.foo.bar = y.foo; - assertEquals( - stringify(y), - "{\n" + - " a: <ref *1> { b: [Circular *1] },\n" + - " foo: <ref *2> { bar: [Circular *2] }\n" + - "}", - ); -}); - -Deno.test(function consoleTestStringifyFunctionWithPrototypeRemoved() { - const f = function f() {}; - Reflect.setPrototypeOf(f, null); - assertEquals(stringify(f), "[Function (null prototype): f]"); - const af = async function af() {}; - Reflect.setPrototypeOf(af, null); - assertEquals(stringify(af), "[AsyncFunction (null prototype): af]"); - const gf = function* gf() {}; - Reflect.setPrototypeOf(gf, null); - assertEquals(stringify(gf), "[GeneratorFunction (null prototype): gf]"); - const agf = async function* agf() {}; - Reflect.setPrototypeOf(agf, null); - assertEquals( - stringify(agf), - "[AsyncGeneratorFunction (null prototype): agf]", - ); -}); - -Deno.test(function consoleTestStringifyFunctionWithProperties() { - const f = () => "test"; - f.x = () => "foo"; - f.y = 3; - f.z = () => "baz"; - f.b = function bar() {}; - f.a = new Map(); - assertEquals( - stringify({ f }), - `{ - f: [Function: f] { - x: [Function (anonymous)], - y: 3, - z: [Function (anonymous)], - b: [Function: bar], - a: Map(0) {} - } -}`, - ); - - const t = () => {}; - t.x = f; - f.s = f; - f.t = t; - assertEquals( - stringify({ f }), - `{ - f: <ref *1> [Function: f] { - x: [Function (anonymous)], - y: 3, - z: [Function (anonymous)], - b: [Function: bar], - a: Map(0) {}, - s: [Circular *1], - t: [Function: t] { x: [Circular *1] } - } -}`, - ); - - assertEquals( - stringify(Array), - `[Function: Array]`, - ); - - assertEquals( - stripColor(Deno.inspect(Array, { showHidden: true })), - `<ref *1> [Function: Array] { - [length]: 1, - [name]: "Array", - [prototype]: Object(0) [ - [length]: 0, - [constructor]: [Circular *1], - [at]: [Function: at] { [length]: 1, [name]: "at" }, - [concat]: [Function: concat] { [length]: 1, [name]: "concat" }, - [copyWithin]: [Function: copyWithin] { [length]: 2, [name]: "copyWithin" }, - [fill]: [Function: fill] { [length]: 1, [name]: "fill" }, - [find]: [Function: find] { [length]: 1, [name]: "find" }, - [findIndex]: [Function: findIndex] { [length]: 1, [name]: "findIndex" }, - [findLast]: [Function: findLast] { [length]: 1, [name]: "findLast" }, - [findLastIndex]: [Function: findLastIndex] { [length]: 1, [name]: "findLastIndex" }, - [lastIndexOf]: [Function: lastIndexOf] { [length]: 1, [name]: "lastIndexOf" }, - [pop]: [Function: pop] { [length]: 0, [name]: "pop" }, - [push]: [Function: push] { [length]: 1, [name]: "push" }, - [reverse]: [Function: reverse] { [length]: 0, [name]: "reverse" }, - [shift]: [Function: shift] { [length]: 0, [name]: "shift" }, - [unshift]: [Function: unshift] { [length]: 1, [name]: "unshift" }, - [slice]: [Function: slice] { [length]: 2, [name]: "slice" }, - [sort]: [Function: sort] { [length]: 1, [name]: "sort" }, - [splice]: [Function: splice] { [length]: 2, [name]: "splice" }, - [includes]: [Function: includes] { [length]: 1, [name]: "includes" }, - [indexOf]: [Function: indexOf] { [length]: 1, [name]: "indexOf" }, - [join]: [Function: join] { [length]: 1, [name]: "join" }, - [keys]: [Function: keys] { [length]: 0, [name]: "keys" }, - [entries]: [Function: entries] { [length]: 0, [name]: "entries" }, - [values]: [Function: values] { [length]: 0, [name]: "values" }, - [forEach]: [Function: forEach] { [length]: 1, [name]: "forEach" }, - [filter]: [Function: filter] { [length]: 1, [name]: "filter" }, - [flat]: [Function: flat] { [length]: 0, [name]: "flat" }, - [flatMap]: [Function: flatMap] { [length]: 1, [name]: "flatMap" }, - [map]: [Function: map] { [length]: 1, [name]: "map" }, - [every]: [Function: every] { [length]: 1, [name]: "every" }, - [some]: [Function: some] { [length]: 1, [name]: "some" }, - [reduce]: [Function: reduce] { [length]: 1, [name]: "reduce" }, - [reduceRight]: [Function: reduceRight] { [length]: 1, [name]: "reduceRight" }, - [toReversed]: [Function: toReversed] { [length]: 0, [name]: "toReversed" }, - [toSorted]: [Function: toSorted] { [length]: 1, [name]: "toSorted" }, - [toSpliced]: [Function: toSpliced] { [length]: 2, [name]: "toSpliced" }, - [with]: [Function: with] { [length]: 2, [name]: "with" }, - [toLocaleString]: [Function: toLocaleString] { [length]: 0, [name]: "toLocaleString" }, - [toString]: [Function: toString] { [length]: 0, [name]: "toString" }, - [Symbol(Symbol.iterator)]: [Function: values] { [length]: 0, [name]: "values" }, - [Symbol(Symbol.unscopables)]: [Object: null prototype] { - at: true, - copyWithin: true, - entries: true, - fill: true, - find: true, - findIndex: true, - findLast: true, - findLastIndex: true, - flat: true, - flatMap: true, - includes: true, - keys: true, - toReversed: true, - toSorted: true, - toSpliced: true, - values: true - } - ], - [isArray]: [Function: isArray] { [length]: 1, [name]: "isArray" }, - [from]: [Function: from] { [length]: 1, [name]: "from" }, - [of]: [Function: of] { [length]: 0, [name]: "of" }, - [fromAsync]: [Function: fromAsync] { [length]: 1, [name]: "fromAsync" }, - [Symbol(Symbol.species)]: [Getter] -}`, - ); -}); - -Deno.test(function consoleTestStringifyWithDepth() { - // deno-lint-ignore no-explicit-any - const nestedObj: any = { a: { b: { c: { d: { e: { f: 42 } } } } } }; - assertEquals( - stripColor(inspectArgs([nestedObj], { depth: 3 })), - "{\n a: { b: { c: { d: [Object] } } }\n}", - ); - assertEquals( - stripColor(inspectArgs([nestedObj], { depth: 4 })), - "{\n a: {\n b: { c: { d: { e: [Object] } } }\n }\n}", - ); - assertEquals( - stripColor(inspectArgs([nestedObj], { depth: 0 })), - "{ a: [Object] }", - ); - assertEquals( - stripColor(inspectArgs([nestedObj])), - "{\n a: {\n b: { c: { d: { e: [Object] } } }\n }\n}", - ); - // test inspect is working the same way - assertEquals( - stripColor(Deno.inspect(nestedObj, { depth: 4 })), - "{\n a: {\n b: { c: { d: { e: [Object] } } }\n }\n}", - ); -}); - -Deno.test(function consoleTestStringifyLargeObject() { - const obj = { - a: 2, - o: { - a: "1", - b: "2", - c: "3", - d: "4", - e: "5", - f: "6", - g: 10, - asd: 2, - asda: 3, - x: { a: "asd", x: 3 }, - }, - }; - assertEquals( - stringify(obj), - `{ - a: 2, - o: { - a: "1", - b: "2", - c: "3", - d: "4", - e: "5", - f: "6", - g: 10, - asd: 2, - asda: 3, - x: { a: "asd", x: 3 } - } -}`, - ); -}); - -Deno.test(function consoleTestStringifyIterable() { - const shortArray = [1, 2, 3, 4, 5]; - assertEquals(stringify(shortArray), "[ 1, 2, 3, 4, 5 ]"); - - const longArray = new Array(200).fill(0); - assertEquals( - stringify(longArray), - `[ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ... 100 more items -]`, - ); - - const obj = { a: "a", longArray }; - assertEquals( - stringify(obj), - `{ - a: "a", - longArray: [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ... 100 more items - ] -}`, - ); - - const shortMap = new Map([ - ["a", 0], - ["b", 1], - ]); - assertEquals(stringify(shortMap), `Map(2) { "a" => 0, "b" => 1 }`); - - const longMap = new Map(); - for (const key of Array(200).keys()) { - longMap.set(`${key}`, key); - } - assertEquals( - stringify(longMap), - `Map(200) { - "0" => 0, - "1" => 1, - "2" => 2, - "3" => 3, - "4" => 4, - "5" => 5, - "6" => 6, - "7" => 7, - "8" => 8, - "9" => 9, - "10" => 10, - "11" => 11, - "12" => 12, - "13" => 13, - "14" => 14, - "15" => 15, - "16" => 16, - "17" => 17, - "18" => 18, - "19" => 19, - "20" => 20, - "21" => 21, - "22" => 22, - "23" => 23, - "24" => 24, - "25" => 25, - "26" => 26, - "27" => 27, - "28" => 28, - "29" => 29, - "30" => 30, - "31" => 31, - "32" => 32, - "33" => 33, - "34" => 34, - "35" => 35, - "36" => 36, - "37" => 37, - "38" => 38, - "39" => 39, - "40" => 40, - "41" => 41, - "42" => 42, - "43" => 43, - "44" => 44, - "45" => 45, - "46" => 46, - "47" => 47, - "48" => 48, - "49" => 49, - "50" => 50, - "51" => 51, - "52" => 52, - "53" => 53, - "54" => 54, - "55" => 55, - "56" => 56, - "57" => 57, - "58" => 58, - "59" => 59, - "60" => 60, - "61" => 61, - "62" => 62, - "63" => 63, - "64" => 64, - "65" => 65, - "66" => 66, - "67" => 67, - "68" => 68, - "69" => 69, - "70" => 70, - "71" => 71, - "72" => 72, - "73" => 73, - "74" => 74, - "75" => 75, - "76" => 76, - "77" => 77, - "78" => 78, - "79" => 79, - "80" => 80, - "81" => 81, - "82" => 82, - "83" => 83, - "84" => 84, - "85" => 85, - "86" => 86, - "87" => 87, - "88" => 88, - "89" => 89, - "90" => 90, - "91" => 91, - "92" => 92, - "93" => 93, - "94" => 94, - "95" => 95, - "96" => 96, - "97" => 97, - "98" => 98, - "99" => 99, - ... 100 more items -}`, - ); - - const shortSet = new Set([1, 2, 3]); - assertEquals(stringify(shortSet), `Set(3) { 1, 2, 3 }`); - const longSet = new Set(); - for (const key of Array(200).keys()) { - longSet.add(key); - } - assertEquals( - stringify(longSet), - `Set(200) { - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99, - ... 100 more items -}`, - ); - - const withEmptyEl = Array(10); - withEmptyEl.fill(0, 4, 6); - assertEquals( - stringify(withEmptyEl), - `[ <4 empty items>, 0, 0, <4 empty items> ]`, - ); - - const emptyArray = Array(5000); - assertEquals( - stringify(emptyArray), - `[ <5000 empty items> ]`, - ); - - assertEquals( - stringify(Array(1)), - `[ <1 empty item> ]`, - ); - - assertEquals( - stringify([, , 1]), - `[ <2 empty items>, 1 ]`, - ); - - assertEquals( - stringify([1, , , 1]), - `[ 1, <2 empty items>, 1 ]`, - ); - - const withEmptyElAndMoreItems = Array(500); - withEmptyElAndMoreItems.fill(0, 50, 80); - withEmptyElAndMoreItems.fill(2, 100, 120); - withEmptyElAndMoreItems.fill(3, 140, 160); - withEmptyElAndMoreItems.fill(4, 180); - assertEquals( - stringify(withEmptyElAndMoreItems), - `[ - <50 empty items>, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, <20 empty items>, - 2, 2, 2, 2, - 2, 2, 2, 2, - 2, 2, 2, 2, - 2, 2, 2, 2, - 2, 2, 2, 2, - <20 empty items>, 3, 3, 3, - 3, 3, 3, 3, - 3, 3, 3, 3, - 3, 3, 3, 3, - 3, 3, 3, 3, - 3, <20 empty items>, 4, 4, - 4, 4, 4, 4, - 4, 4, 4, 4, - 4, 4, 4, 4, - 4, 4, 4, 4, - 4, 4, 4, 4, - 4, 4, 4, 4, - ... 294 more items -]`, - ); - - const lWithEmptyEl = Array(200); - lWithEmptyEl.fill(0, 50, 80); - assertEquals( - stringify(lWithEmptyEl), - `[ - <50 empty items>, 0, 0, - 0, 0, 0, - 0, 0, 0, - 0, 0, 0, - 0, 0, 0, - 0, 0, 0, - 0, 0, 0, - 0, 0, 0, - 0, 0, 0, - 0, 0, 0, - 0, <120 empty items> -]`, - ); -}); - -Deno.test(function consoleTestStringifyIterableWhenGrouped() { - const withOddNumberOfEls = new Float64Array( - [ - 2.1, - 2.01, - 2.001, - 2.0001, - 2.00001, - 2.000001, - 2.0000001, - 2.00000001, - 2.000000001, - 2.0000000001, - 2, - ], - ); - assertEquals( - stringify(withOddNumberOfEls), - `Float64Array(11) [ - 2.1, 2.01, - 2.001, 2.0001, - 2.00001, 2.000001, - 2.0000001, 2.00000001, - 2.000000001, 2.0000000001, - 2 -]`, - ); - const withEvenNumberOfEls = new Float64Array( - [ - 2.1, - 2.01, - 2.001, - 2.0001, - 2.00001, - 2.000001, - 2.0000001, - 2.00000001, - 2.000000001, - 2.0000000001, - 2, - 2, - ], - ); - assertEquals( - stringify(withEvenNumberOfEls), - `Float64Array(12) [ - 2.1, 2.01, - 2.001, 2.0001, - 2.00001, 2.000001, - 2.0000001, 2.00000001, - 2.000000001, 2.0000000001, - 2, 2 -]`, - ); - const withThreeColumns = [ - 2, - 2.1, - 2.11, - 2, - 2.111, - 2.1111, - 2, - 2.1, - 2.11, - 2, - 2.1, - ]; - assertEquals( - stringify(withThreeColumns), - `[ - 2, 2.1, 2.11, - 2, 2.111, 2.1111, - 2, 2.1, 2.11, - 2, 2.1 -]`, - ); -}); - -Deno.test(function consoleTestIteratorValueAreNotConsumed() { - const setIterator = new Set([1, 2, 3]).values(); - assertEquals( - stringify(setIterator), - "[Set Iterator] { 1, 2, 3 }", - ); - assertEquals([...setIterator], [1, 2, 3]); -}); - -Deno.test(function consoleTestWeakSetAndWeakMapWithShowHidden() { - assertEquals( - stripColor(Deno.inspect(new WeakSet([{}]), { showHidden: true })), - "WeakSet { {} }", - ); - assertEquals( - stripColor(Deno.inspect(new WeakMap([[{}, "foo"]]), { showHidden: true })), - 'WeakMap { {} => "foo" }', - ); -}); - -Deno.test(async function consoleTestStringifyPromises() { - const pendingPromise = new Promise((_res, _rej) => {}); - assertEquals(stringify(pendingPromise), "Promise { <pending> }"); - - const resolvedPromise = new Promise((res, _rej) => { - res("Resolved!"); - }); - assertEquals(stringify(resolvedPromise), `Promise { "Resolved!" }`); - - let rejectedPromise; - try { - rejectedPromise = new Promise((_, rej) => { - rej(Error("Whoops")); - }); - await rejectedPromise; - } catch (_err) { - // pass - } - const strLines = stringify(rejectedPromise).split("\n"); - assertEquals(strLines[0], "Promise {"); - assertEquals(strLines[1], " <rejected> Error: Whoops"); -}); - -Deno.test(function consoleTestWithCustomInspector() { - class A { - [customInspect]( - inspect: unknown, - options: Deno.InspectOptions, - ): string { - assertEquals(typeof inspect, "function"); - assertEquals(typeof options, "object"); - return "b"; - } - } - - assertEquals(stringify(new A()), "b"); -}); - -Deno.test(function consoleTestWithCustomInspectorUsingInspectFunc() { - class A { - [customInspect]( - inspect: (v: unknown, opts?: Deno.InspectOptions) => string, - ): string { - return "b " + inspect({ c: 1 }); - } - } - - assertEquals(stringify(new A()), "b { c: 1 }"); -}); - -Deno.test(function consoleTestWithConstructorError() { - const obj = new Proxy({}, { - getOwnPropertyDescriptor(_target, name) { - if (name == "constructor") { - throw "yikes"; - } - return undefined; - }, - }); - assertEquals(Deno.inspect(obj), "{}"); -}); - -Deno.test(function consoleTestWithCustomInspectorError() { - class A { - [customInspect](): never { - throw new Error("BOOM"); - } - } - - const a = new A(); - assertThrows( - () => stringify(a), - Error, - "BOOM", - "Custom inspect won't attempt to parse if user defined function throws", - ); - assertThrows( - () => stringify(a), - Error, - "BOOM", - "Inspect should fail and maintain a clear CTX_STACK", - ); -}); - -Deno.test(function consoleTestWithCustomInspectFunction() { - function a() {} - Object.assign(a, { - [customInspect]() { - return "b"; - }, - }); - - assertEquals(stringify(a), "b"); -}); - -Deno.test(function consoleTestWithIntegerFormatSpecifier() { - assertEquals(stringify("%i"), "%i"); - assertEquals(stringify("%i", 42.0), "42"); - assertEquals(stringify("%i", 42), "42"); - assertEquals(stringify("%i", "42"), "NaN"); - assertEquals(stringify("%i", 1.5), "1"); - assertEquals(stringify("%i", -0.5), "0"); - assertEquals(stringify("%i", ""), "NaN"); - assertEquals(stringify("%i", Symbol()), "NaN"); - assertEquals(stringify("%i %d", 42, 43), "42 43"); - assertEquals(stringify("%d %i", 42), "42 %i"); - assertEquals(stringify("%d", 12345678901234567890123), "1"); - assertEquals( - stringify("%i", 12345678901234567890123n), - "12345678901234567890123n", - ); -}); - -Deno.test(function consoleTestWithFloatFormatSpecifier() { - assertEquals(stringify("%f"), "%f"); - assertEquals(stringify("%f", 42.0), "42"); - assertEquals(stringify("%f", 42), "42"); - assertEquals(stringify("%f", "42"), "NaN"); - assertEquals(stringify("%f", 1.5), "1.5"); - assertEquals(stringify("%f", -0.5), "-0.5"); - assertEquals(stringify("%f", Math.PI), "3.141592653589793"); - assertEquals(stringify("%f", ""), "NaN"); - assertEquals(stringify("%f", Symbol("foo")), "NaN"); - assertEquals(stringify("%f", 5n), "NaN"); - assertEquals(stringify("%f %f", 42, 43), "42 43"); - assertEquals(stringify("%f %f", 42), "42 %f"); -}); - -Deno.test(function consoleTestWithStringFormatSpecifier() { - assertEquals(stringify("%s"), "%s"); - assertEquals(stringify("%s", undefined), "undefined"); - assertEquals(stringify("%s", "foo"), "foo"); - assertEquals(stringify("%s", 42), "42"); - assertEquals(stringify("%s", "42"), "42"); - assertEquals(stringify("%s %s", 42, 43), "42 43"); - assertEquals(stringify("%s %s", 42), "42 %s"); - assertEquals(stringify("%s", Symbol("foo")), "Symbol(foo)"); -}); - -Deno.test(function consoleTestWithObjectFormatSpecifier() { - assertEquals(stringify("%o"), "%o"); - assertEquals(stringify("%o", 42), "42"); - assertEquals(stringify("%o", "foo"), `"foo"`); - assertEquals(stringify("o: %o, a: %O", {}, []), "o: {}, a: []"); - assertEquals(stringify("%o", { a: 42 }), "{ a: 42 }"); - assertEquals( - stringify("%o", { a: { b: { c: { d: new Set([1]) } } } }), - "{\n a: {\n b: { c: { d: Set(1) { 1 } } }\n }\n}", - ); -}); - -Deno.test(function consoleTestWithStyleSpecifier() { - assertEquals(stringify("%cfoo%cbar"), "%cfoo%cbar"); - assertEquals(stringify("%cfoo%cbar", ""), "foo%cbar"); - assertEquals(stripColor(stringify("%cfoo%cbar", "", "color: red")), "foobar"); -}); - -Deno.test(function consoleParseCssColor() { - assertEquals(parseCssColor("inherit"), null); - assertEquals(parseCssColor("black"), [0, 0, 0]); - assertEquals(parseCssColor("darkmagenta"), [139, 0, 139]); - assertEquals(parseCssColor("slateblue"), [106, 90, 205]); - assertEquals(parseCssColor("#ffaa00"), [255, 170, 0]); - assertEquals(parseCssColor("#ffAA00"), [255, 170, 0]); - assertEquals(parseCssColor("#fa0"), [255, 170, 0]); - assertEquals(parseCssColor("#FA0"), [255, 170, 0]); - assertEquals(parseCssColor("#18d"), [17, 136, 221]); - assertEquals(parseCssColor("#18D"), [17, 136, 221]); - assertEquals(parseCssColor("#1188DD"), [17, 136, 221]); - assertEquals(parseCssColor("rgb(100, 200, 50)"), [100, 200, 50]); - assertEquals(parseCssColor("rgb(+100.3, -200, .5)"), [100, 0, 1]); - assertEquals(parseCssColor("hsl(75, 60%, 40%)"), [133, 163, 41]); - - assertEquals(parseCssColor("rgb(100,200,50)"), [100, 200, 50]); - assertEquals( - parseCssColor("rgb( \t\n100 \t\n, \t\n200 \t\n, \t\n50 \t\n)"), - [100, 200, 50], - ); -}); - -Deno.test(function consoleParseCss() { - assertEquals( - parseCss("background-color: inherit"), - { ...DEFAULT_CSS, backgroundColor: "inherit" }, - ); - assertEquals( - parseCss("color: inherit"), - { ...DEFAULT_CSS, color: "inherit" }, - ); - assertEquals( - parseCss("background-color: red"), - { ...DEFAULT_CSS, backgroundColor: "red" }, - ); - assertEquals(parseCss("color: blue"), { ...DEFAULT_CSS, color: "blue" }); - assertEquals( - parseCss("font-weight: bold"), - { ...DEFAULT_CSS, fontWeight: "bold" }, - ); - assertEquals( - parseCss("font-style: italic"), - { ...DEFAULT_CSS, fontStyle: "italic" }, - ); - assertEquals( - parseCss("font-style: oblique"), - { ...DEFAULT_CSS, fontStyle: "italic" }, - ); - assertEquals( - parseCss("text-decoration-color: green"), - { ...DEFAULT_CSS, textDecorationColor: [0, 128, 0] }, - ); - assertEquals( - parseCss("text-decoration-line: underline overline line-through"), - { - ...DEFAULT_CSS, - textDecorationLine: ["underline", "overline", "line-through"], - }, - ); - assertEquals( - parseCss("text-decoration: yellow underline"), - { - ...DEFAULT_CSS, - textDecorationColor: [255, 255, 0], - textDecorationLine: ["underline"], - }, - ); - - assertEquals( - parseCss("color:red;font-weight:bold;"), - { ...DEFAULT_CSS, color: "red", fontWeight: "bold" }, - ); - assertEquals( - parseCss( - " \t\ncolor \t\n: \t\nred \t\n; \t\nfont-weight \t\n: \t\nbold \t\n; \t\n", - ), - { ...DEFAULT_CSS, color: "red", fontWeight: "bold" }, - ); - assertEquals( - parseCss("color: red; font-weight: bold, font-style: italic"), - { ...DEFAULT_CSS, color: "red" }, - ); -}); - -Deno.test(function consoleCssToAnsi() { - assertEquals( - cssToAnsiEsc({ ...DEFAULT_CSS, backgroundColor: "inherit" }), - "_[49m", - ); - assertEquals( - cssToAnsiEsc({ ...DEFAULT_CSS, backgroundColor: "foo" }), - "_[49m", - ); - assertEquals( - cssToAnsiEsc({ ...DEFAULT_CSS, backgroundColor: "black" }), - "_[40m", - ); - assertEquals( - cssToAnsiEsc({ ...DEFAULT_CSS, color: "inherit" }), - "_[39m", - ); - assertEquals( - cssToAnsiEsc({ ...DEFAULT_CSS, color: "blue" }), - "_[34m", - ); - assertEquals( - cssToAnsiEsc({ ...DEFAULT_CSS, backgroundColor: [200, 201, 202] }), - "_[48;2;200;201;202m", - ); - assertEquals( - cssToAnsiEsc({ ...DEFAULT_CSS, color: [203, 204, 205] }), - "_[38;2;203;204;205m", - ); - assertEquals(cssToAnsiEsc({ ...DEFAULT_CSS, fontWeight: "bold" }), "_[1m"); - assertEquals(cssToAnsiEsc({ ...DEFAULT_CSS, fontStyle: "italic" }), "_[3m"); - assertEquals( - cssToAnsiEsc({ ...DEFAULT_CSS, textDecorationColor: [206, 207, 208] }), - "_[58;2;206;207;208m", - ); - assertEquals( - cssToAnsiEsc({ ...DEFAULT_CSS, textDecorationLine: ["underline"] }), - "_[4m", - ); - assertEquals( - cssToAnsiEsc( - { ...DEFAULT_CSS, textDecorationLine: ["overline", "line-through"] }, - ), - "_[9m_[53m", - ); - assertEquals( - cssToAnsiEsc( - { ...DEFAULT_CSS, color: [203, 204, 205], fontWeight: "bold" }, - ), - "_[38;2;203;204;205m_[1m", - ); - assertEquals( - cssToAnsiEsc( - { ...DEFAULT_CSS, color: [0, 0, 0], fontWeight: "bold" }, - { ...DEFAULT_CSS, color: [203, 204, 205], fontStyle: "italic" }, - ), - "_[38;2;0;0;0m_[1m_[23m", - ); -}); - -Deno.test(function consoleTestWithVariousOrInvalidFormatSpecifier() { - assertEquals(stringify("%s:%s"), "%s:%s"); - assertEquals(stringify("%i:%i"), "%i:%i"); - assertEquals(stringify("%d:%d"), "%d:%d"); - assertEquals(stringify("%%s%s", "foo"), "%sfoo"); - assertEquals(stringify("%s:%s", undefined), "undefined:%s"); - assertEquals(stringify("%s:%s", "foo", "bar"), "foo:bar"); - assertEquals(stringify("%s:%s", "foo", "bar", "baz"), "foo:bar baz"); - assertEquals(stringify("%%%s%%", "hi"), "%hi%"); - assertEquals(stringify("%d:%d", 12), "12:%d"); - assertEquals(stringify("%i:%i", 12), "12:%i"); - assertEquals(stringify("%f:%f", 12), "12:%f"); - assertEquals(stringify("o: %o, a: %o", {}), "o: {}, a: %o"); - assertEquals(stringify("abc%", 1), "abc% 1"); -}); - -Deno.test(function consoleTestCallToStringOnLabel() { - const methods = ["count", "countReset", "time", "timeLog", "timeEnd"]; - mockConsole((console) => { - for (const method of methods) { - let hasCalled = false; - console[method]({ - toString() { - hasCalled = true; - }, - }); - assertEquals(hasCalled, true); - } - }); -}); - -Deno.test(function consoleTestError() { - class MyError extends Error { - constructor(errStr: string) { - super(errStr); - this.name = "MyError"; - } - } - try { - throw new MyError("This is an error"); - } catch (e) { - assert( - stringify(e) - .split("\n")[0] // error has been caught - .includes("MyError: This is an error"), - ); - } -}); - -Deno.test(function consoleTestClear() { - mockConsole((console, out) => { - console.clear(); - assertEquals(out.toString(), "\x1b[1;1H" + "\x1b[0J"); - }); -}); - -// Test bound this issue -Deno.test(function consoleDetachedLog() { - mockConsole((console) => { - const log = console.log; - const dir = console.dir; - const dirxml = console.dirxml; - const debug = console.debug; - const info = console.info; - const warn = console.warn; - const error = console.error; - const consoleAssert = console.assert; - const consoleCount = console.count; - const consoleCountReset = console.countReset; - const consoleTable = console.table; - const consoleTime = console.time; - const consoleTimeLog = console.timeLog; - const consoleTimeEnd = console.timeEnd; - const consoleGroup = console.group; - const consoleGroupEnd = console.groupEnd; - const consoleClear = console.clear; - log("Hello world"); - dir("Hello world"); - dirxml("Hello world"); - debug("Hello world"); - info("Hello world"); - warn("Hello world"); - error("Hello world"); - consoleAssert(true); - consoleCount("Hello world"); - consoleCountReset("Hello world"); - consoleTable({ test: "Hello world" }); - consoleTime("Hello world"); - consoleTimeLog("Hello world"); - consoleTimeEnd("Hello world"); - consoleGroup("Hello world"); - consoleGroupEnd(); - consoleClear(); - }); -}); - -class StringBuffer { - chunks: string[] = []; - add(x: string) { - this.chunks.push(x); - } - toString(): string { - return this.chunks.join(""); - } -} - -type ConsoleExamineFunc = ( - // deno-lint-ignore no-explicit-any - csl: any, - out: StringBuffer, - err?: StringBuffer, - both?: StringBuffer, -) => void; - -function mockConsole(f: ConsoleExamineFunc) { - const out = new StringBuffer(); - const err = new StringBuffer(); - const both = new StringBuffer(); - const csl = new Console( - (x: string, level: number, printsNewLine: boolean) => { - const content = x + (printsNewLine ? "\n" : ""); - const buf = level > 1 ? err : out; - buf.add(content); - both.add(content); - }, - ); - f(csl, out, err, both); -} - -// console.group test -Deno.test(function consoleGroup() { - mockConsole((console, out) => { - console.group("1"); - console.log("2"); - console.group("3"); - console.log("4"); - console.groupEnd(); - console.groupEnd(); - console.log("5"); - console.log("6"); - - assertEquals( - out.toString(), - `1 - 2 - 3 - 4 -5 -6 -`, - ); - }); -}); - -// console.group with console.warn test -Deno.test(function consoleGroupWarn() { - mockConsole((console, _out, _err, both) => { - assert(both); - console.warn("1"); - console.group(); - console.warn("2"); - console.group(); - console.warn("3"); - console.groupEnd(); - console.warn("4"); - console.groupEnd(); - console.warn("5"); - - console.warn("6"); - console.warn("7"); - assertEquals( - both.toString(), - `1 - 2 - 3 - 4 -5 -6 -7 -`, - ); - }); -}); - -// console.table test -Deno.test(function consoleTable() { - mockConsole((console, out) => { - console.table({ a: "test", b: 1 }); - assertEquals( - stripColor(out.toString()), - `\ -┌───────┬────────┐ -│ (idx) │ Values │ -├───────┼────────┤ -│ a │ "test" │ -│ b │ 1 │ -└───────┴────────┘ -`, - ); - }); - mockConsole((console, out) => { - console.table({ a: { b: 10 }, b: { b: 20, c: 30 } }, ["c"]); - assertEquals( - stripColor(out.toString()), - `\ -┌───────┬────┐ -│ (idx) │ c │ -├───────┼────┤ -│ a │ │ -│ b │ 30 │ -└───────┴────┘ -`, - ); - }); - mockConsole((console, out) => { - console.table([[1, 1], [234, 2.34], [56789, 56.789]]); - assertEquals( - stripColor(out.toString()), - `\ -┌───────┬───────┬────────┐ -│ (idx) │ 0 │ 1 │ -├───────┼───────┼────────┤ -│ 0 │ 1 │ 1 │ -│ 1 │ 234 │ 2.34 │ -│ 2 │ 56789 │ 56.789 │ -└───────┴───────┴────────┘ -`, - ); - }); - mockConsole((console, out) => { - console.table([1, 2, [3, [4]], [5, 6], [[7], [8]]]); - assertEquals( - stripColor(out.toString()), - `\ -┌───────┬───────┬───────┬────────┐ -│ (idx) │ 0 │ 1 │ Values │ -├───────┼───────┼───────┼────────┤ -│ 0 │ │ │ 1 │ -│ 1 │ │ │ 2 │ -│ 2 │ 3 │ [ 4 ] │ │ -│ 3 │ 5 │ 6 │ │ -│ 4 │ [ 7 ] │ [ 8 ] │ │ -└───────┴───────┴───────┴────────┘ -`, - ); - }); - mockConsole((console, out) => { - console.table(new Set([1, 2, 3, "test"])); - assertEquals( - stripColor(out.toString()), - `\ -┌────────────┬────────┐ -│ (iter idx) │ Values │ -├────────────┼────────┤ -│ 0 │ 1 │ -│ 1 │ 2 │ -│ 2 │ 3 │ -│ 3 │ "test" │ -└────────────┴────────┘ -`, - ); - }); - mockConsole((console, out) => { - console.table( - new Map([ - [1, "one"], - [2, "two"], - ]), - ); - assertEquals( - stripColor(out.toString()), - `\ -┌────────────┬─────┬────────┐ -│ (iter idx) │ Key │ Values │ -├────────────┼─────┼────────┤ -│ 0 │ 1 │ "one" │ -│ 1 │ 2 │ "two" │ -└────────────┴─────┴────────┘ -`, - ); - }); - mockConsole((console, out) => { - console.table({ - a: true, - b: { c: { d: 10 }, e: [1, 2, [5, 6]] }, - f: "test", - g: new Set([1, 2, 3, "test"]), - h: new Map([[1, "one"]]), - }); - assertEquals( - stripColor(out.toString()), - `\ -┌───────┬───────────┬────────────────────┬────────┐ -│ (idx) │ c │ e │ Values │ -├───────┼───────────┼────────────────────┼────────┤ -│ a │ │ │ true │ -│ b │ { d: 10 } │ [ 1, 2, [ 5, 6 ] ] │ │ -│ f │ │ │ "test" │ -│ g │ │ │ │ -│ h │ │ │ │ -└───────┴───────────┴────────────────────┴────────┘ -`, - ); - }); - mockConsole((console, out) => { - console.table([ - 1, - "test", - false, - { a: 10 }, - ["test", { b: 20, c: "test" }], - ]); - assertEquals( - stripColor(out.toString()), - `\ -┌───────┬────────┬──────────────────────┬────┬────────┐ -│ (idx) │ 0 │ 1 │ a │ Values │ -├───────┼────────┼──────────────────────┼────┼────────┤ -│ 0 │ │ │ │ 1 │ -│ 1 │ │ │ │ "test" │ -│ 2 │ │ │ │ false │ -│ 3 │ │ │ 10 │ │ -│ 4 │ "test" │ { b: 20, c: "test" } │ │ │ -└───────┴────────┴──────────────────────┴────┴────────┘ -`, - ); - }); - mockConsole((console, out) => { - console.table([]); - assertEquals( - stripColor(out.toString()), - `\ -┌───────┐ -│ (idx) │ -├───────┤ -└───────┘ -`, - ); - }); - mockConsole((console, out) => { - console.table({}); - assertEquals( - stripColor(out.toString()), - `\ -┌───────┐ -│ (idx) │ -├───────┤ -└───────┘ -`, - ); - }); - mockConsole((console, out) => { - console.table(new Set()); - assertEquals( - stripColor(out.toString()), - `\ -┌────────────┐ -│ (iter idx) │ -├────────────┤ -└────────────┘ -`, - ); - }); - mockConsole((console, out) => { - console.table(new Map()); - assertEquals( - stripColor(out.toString()), - `\ -┌────────────┐ -│ (iter idx) │ -├────────────┤ -└────────────┘ -`, - ); - }); - mockConsole((console, out) => { - console.table("test"); - assertEquals(out.toString(), "test\n"); - }); - mockConsole((console, out) => { - console.table(["Hello", "你好", "Amapá"]); - assertEquals( - stripColor(out.toString()), - `\ -┌───────┬─────────┐ -│ (idx) │ Values │ -├───────┼─────────┤ -│ 0 │ "Hello" │ -│ 1 │ "你好" │ -│ 2 │ "Amapá" │ -└───────┴─────────┘ -`, - ); - }); - mockConsole((console, out) => { - console.table([ - [1, 2], - [3, 4], - ]); - assertEquals( - stripColor(out.toString()), - `\ -┌───────┬───┬───┐ -│ (idx) │ 0 │ 1 │ -├───────┼───┼───┤ -│ 0 │ 1 │ 2 │ -│ 1 │ 3 │ 4 │ -└───────┴───┴───┘ -`, - ); - }); - mockConsole((console, out) => { - console.table({ 1: { a: 4, b: 5 }, 2: null, 3: { b: 6, c: 7 } }, ["b"]); - assertEquals( - stripColor(out.toString()), - `\ -┌───────┬───┐ -│ (idx) │ b │ -├───────┼───┤ -│ 1 │ 5 │ -│ 2 │ │ -│ 3 │ 6 │ -└───────┴───┘ -`, - ); - }); - mockConsole((console, out) => { - console.table([{ a: 0 }, { a: 1, b: 1 }, { a: 2 }, { a: 3, b: 3 }]); - assertEquals( - stripColor(out.toString()), - `\ -┌───────┬───┬───┐ -│ (idx) │ a │ b │ -├───────┼───┼───┤ -│ 0 │ 0 │ │ -│ 1 │ 1 │ 1 │ -│ 2 │ 2 │ │ -│ 3 │ 3 │ 3 │ -└───────┴───┴───┘ -`, - ); - }); - mockConsole((console, out) => { - console.table( - [{ a: 0 }, { a: 1, c: 1 }, { a: 2 }, { a: 3, c: 3 }], - ["a", "b", "c"], - ); - assertEquals( - stripColor(out.toString()), - `\ -┌───────┬───┬───┬───┐ -│ (idx) │ a │ b │ c │ -├───────┼───┼───┼───┤ -│ 0 │ 0 │ │ │ -│ 1 │ 1 │ │ 1 │ -│ 2 │ 2 │ │ │ -│ 3 │ 3 │ │ 3 │ -└───────┴───┴───┴───┘ -`, - ); - }); -}); - -// console.log(Error) test -Deno.test(function consoleLogShouldNotThrowError() { - mockConsole((console) => { - let result = 0; - try { - console.log(new Error("foo")); - result = 1; - } catch (_e) { - result = 2; - } - assertEquals(result, 1); - }); - - // output errors to the console should not include "Uncaught" - mockConsole((console, out) => { - console.log(new Error("foo")); - assertEquals(out.toString().includes("Uncaught"), false); - }); -}); - -Deno.test(function consoleLogShouldNotThrowErrorWhenInvalidCssColorsAreGiven() { - mockConsole((console, out) => { - console.log("%cfoo", "color: foo; background-color: bar;"); - assertEquals(stripColor(out.toString()), "foo\n"); - }); -}); - -// console.log(Invalid Date) test -Deno.test(function consoleLogShouldNotThrowErrorWhenInvalidDateIsPassed() { - mockConsole((console, out) => { - const invalidDate = new Date("test"); - console.log(invalidDate); - assertEquals(stripColor(out.toString()), "Invalid Date\n"); - }); -}); - -// console.log(new Proxy(new Set(), {})) -Deno.test(function consoleLogShouldNotThrowErrorWhenInputIsProxiedSet() { - mockConsole((console, out) => { - const proxiedSet = new Proxy(new Set([1, 2]), {}); - console.log(proxiedSet); - assertEquals(stripColor(out.toString()), "Set(2) { 1, 2 }\n"); - }); -}); - -// console.log(new Proxy(new Map(), {})) -Deno.test(function consoleLogShouldNotThrowErrorWhenInputIsProxiedMap() { - mockConsole((console, out) => { - const proxiedMap = new Proxy(new Map([[1, 1], [2, 2]]), {}); - console.log(proxiedMap); - assertEquals(stripColor(out.toString()), "Map(2) { 1 => 1, 2 => 2 }\n"); - }); -}); - -// console.log(new Proxy(new Uint8Array(), {})) -Deno.test(function consoleLogShouldNotThrowErrorWhenInputIsProxiedTypedArray() { - mockConsole((console, out) => { - const proxiedUint8Array = new Proxy(new Uint8Array([1, 2]), {}); - console.log(proxiedUint8Array); - assertEquals(stripColor(out.toString()), "Uint8Array(2) [ 1, 2 ]\n"); - }); -}); - -// console.log(new Proxy(new RegExp(), {})) -Deno.test(function consoleLogShouldNotThrowErrorWhenInputIsProxiedRegExp() { - mockConsole((console, out) => { - const proxiedRegExp = new Proxy(/aaaa/, {}); - console.log(proxiedRegExp); - assertEquals(stripColor(out.toString()), "/aaaa/\n"); - }); -}); - -// console.log(new Proxy(new Date(), {})) -Deno.test(function consoleLogShouldNotThrowErrorWhenInputIsProxiedDate() { - mockConsole((console, out) => { - const proxiedDate = new Proxy(new Date("2022-09-24T15:59:39.529Z"), {}); - console.log(proxiedDate); - assertEquals(stripColor(out.toString()), "2022-09-24T15:59:39.529Z\n"); - }); -}); - -// console.log(new Proxy(new Error(), {})) -Deno.test(function consoleLogShouldNotThrowErrorWhenInputIsProxiedError() { - mockConsole((console, out) => { - const proxiedError = new Proxy(new Error("message"), {}); - console.log(proxiedError); - assertStringIncludes(stripColor(out.toString()), "Error: message\n"); - }); -}); - -// console.dir test -Deno.test(function consoleDir() { - mockConsole((console, out) => { - console.dir("DIR"); - assertEquals(out.toString(), "DIR\n"); - }); - mockConsole((console, out) => { - console.dir("DIR", { indentLevel: 2 }); - assertEquals(out.toString(), " DIR\n"); - }); -}); - -// console.dir test -Deno.test(function consoleDirXml() { - mockConsole((console, out) => { - console.dirxml("DIRXML"); - assertEquals(out.toString(), "DIRXML\n"); - }); - mockConsole((console, out) => { - console.dirxml("DIRXML", { indentLevel: 2 }); - assertEquals(out.toString(), " DIRXML\n"); - }); -}); - -// console.trace test -Deno.test(function consoleTrace() { - mockConsole((console, _out, err) => { - console.trace("%s", "custom message"); - assert(err); - assert(err.toString().includes("Trace: custom message")); - }); -}); - -Deno.test(function inspectString() { - assertEquals( - stripColor(Deno.inspect("\0")), - `"\\x00"`, - ); - assertEquals( - stripColor(Deno.inspect("\x1b[2J")), - `"\\x1b[2J"`, - ); -}); - -Deno.test(function inspectGetters() { - assertEquals( - stripColor(Deno.inspect({ - get foo() { - return 0; - }, - })), - "{ foo: [Getter] }", - ); - - assertEquals( - stripColor(Deno.inspect({ - get foo() { - return 0; - }, - }, { getters: true })), - "{ foo: [Getter: 0] }", - ); - - assertEquals( - Deno.inspect({ - get foo() { - throw new Error("bar"); - }, - }, { getters: true }), - "{ foo: [Getter: <Inspection threw (bar)>] }", - ); -}); - -Deno.test(function inspectPrototype() { - class A {} - assertEquals(Deno.inspect(A.prototype), "{}"); -}); - -Deno.test(function inspectSorted() { - assertEquals( - stripColor(Deno.inspect({ b: 2, a: 1 }, { sorted: true })), - "{ a: 1, b: 2 }", - ); - assertEquals( - stripColor(Deno.inspect(new Set(["b", "a"]), { sorted: true })), - `Set(2) { "a", "b" }`, - ); - assertEquals( - stripColor(Deno.inspect( - new Map([ - ["b", 2], - ["a", 1], - ]), - { sorted: true }, - )), - `Map(2) { "a" => 1, "b" => 2 }`, - ); -}); - -Deno.test(function inspectTrailingComma() { - assertEquals( - stripColor(Deno.inspect( - [ - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - ], - { trailingComma: true }, - )), - `[ - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", -]`, - ); - assertEquals( - stripColor(Deno.inspect( - { - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: 1, - bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: 2, - }, - { trailingComma: true }, - )), - `{ - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: 1, - bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: 2, -}`, - ); - assertEquals( - stripColor(Deno.inspect( - new Set([ - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - ]), - { trailingComma: true }, - )), - `Set(2) { - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", -}`, - ); - assertEquals( - stripColor(Deno.inspect( - new Map([ - ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1], - ["bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 2], - ]), - { trailingComma: true }, - )), - `Map(2) { - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" => 1, - "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" => 2, -}`, - ); -}); - -Deno.test(function inspectCompact() { - assertEquals( - stripColor(Deno.inspect({ a: 1, b: 2 }, { compact: false })), - `{ - a: 1, - b: 2 -}`, - ); -}); - -Deno.test(function inspectIterableLimit() { - assertEquals( - stripColor(Deno.inspect(["a", "b", "c"], { iterableLimit: 2 })), - `[ "a", "b", ... 1 more item ]`, - ); - assertEquals( - stripColor(Deno.inspect(new Set(["a", "b", "c"]), { iterableLimit: 2 })), - `Set(3) { "a", "b", ... 1 more item }`, - ); - assertEquals( - stripColor(Deno.inspect( - new Map([ - ["a", 1], - ["b", 2], - ["c", 3], - ]), - { iterableLimit: 2 }, - )), - `Map(3) { "a" => 1, "b" => 2, ... 1 more item }`, - ); -}); - -Deno.test(function inspectProxy() { - assertEquals( - stripColor(Deno.inspect( - new Proxy([1, 2, 3], {}), - )), - "[ 1, 2, 3 ]", - ); - assertEquals( - stripColor(Deno.inspect( - new Proxy({ key: "value" }, {}), - )), - `{ key: "value" }`, - ); - assertEquals( - stripColor(Deno.inspect( - new Proxy({}, { - get(_target, key) { - if (key === Symbol.toStringTag) { - return "MyProxy"; - } else { - return 5; - } - }, - getOwnPropertyDescriptor() { - return { - enumerable: true, - configurable: true, - value: 5, - }; - }, - ownKeys() { - return ["prop1", "prop2"]; - }, - }), - )), - `Object [MyProxy] { prop1: 5, prop2: 5 }`, - ); - assertEquals( - stripColor(Deno.inspect( - new Proxy([1, 2, 3], { get() {} }), - { showProxy: true }, - )), - "Proxy [ [ 1, 2, 3 ], { get: [Function: get] } ]", - ); - assertEquals( - stripColor(Deno.inspect( - new Proxy({ a: 1 }, { - set(): boolean { - return false; - }, - }), - { showProxy: true }, - )), - "Proxy [ { a: 1 }, { set: [Function: set] } ]", - ); - assertEquals( - stripColor(Deno.inspect( - new Proxy([1, 2, 3, 4, 5, 6, 7], { get() {} }), - { showProxy: true }, - )), - `Proxy [ - [ - 1, 2, 3, 4, - 5, 6, 7 - ], - { get: [Function: get] } -]`, - ); - assertEquals( - stripColor(Deno.inspect( - new Proxy(function fn() {}, { get() {} }), - { showProxy: true }, - )), - "Proxy [ [Function: fn], { get: [Function: get] } ]", - ); -}); - -Deno.test(function inspectError() { - const error1 = new Error("This is an error"); - const error2 = new Error("This is an error", { - cause: new Error("This is a cause error"), - }); - - assertStringIncludes( - stripColor(Deno.inspect(error1)), - "Error: This is an error", - ); - assertStringIncludes( - stripColor(Deno.inspect(error2)), - "Error: This is an error", - ); - assertStringIncludes( - stripColor(Deno.inspect(error2)), - "Caused by Error: This is a cause error", - ); -}); - -Deno.test(function inspectErrorCircular() { - const error1 = new Error("This is an error"); - const error2 = new Error("This is an error", { - cause: new Error("This is a cause error"), - }); - error1.cause = error1; - assert(error2.cause instanceof Error); - error2.cause.cause = error2; - - assertStringIncludes( - stripColor(Deno.inspect(error1)), - "Error: This is an error", - ); - assertStringIncludes( - stripColor(Deno.inspect(error2)), - "<ref *1> Error: This is an error", - ); - assertStringIncludes( - stripColor(Deno.inspect(error2)), - "Caused by Error: This is a cause error", - ); - assertStringIncludes( - stripColor(Deno.inspect(error2)), - "Caused by [Circular *1]", - ); -}); - -Deno.test(function inspectColors() { - assertEquals(Deno.inspect(1), "1"); - assertStringIncludes(Deno.inspect(1, { colors: true }), "\x1b["); -}); - -Deno.test(function inspectEmptyArray() { - const arr: string[] = []; - - assertEquals( - Deno.inspect(arr, { - compact: false, - trailingComma: true, - }), - "[]", - ); -}); - -Deno.test(function inspectDeepEmptyArray() { - const obj = { - arr: [], - }; - - assertEquals( - Deno.inspect(obj, { - compact: false, - trailingComma: true, - }), - `{ - arr: [], -}`, - ); -}); - -Deno.test(function inspectEmptyMap() { - const map = new Map(); - - assertEquals( - Deno.inspect(map, { - compact: false, - trailingComma: true, - }), - "Map(0) {}", - ); -}); - -Deno.test(function inspectEmptySet() { - const set = new Set(); - - assertEquals( - Deno.inspect(set, { - compact: false, - trailingComma: true, - }), - "Set(0) {}", - ); -}); - -Deno.test(function inspectEmptyUint8Array() { - const typedArray = new Uint8Array(0); - - assertEquals( - Deno.inspect(typedArray, { - compact: false, - trailingComma: true, - }), - "Uint8Array(0) []", - ); -}); - -Deno.test(function inspectLargeArrayBuffer() { - const arrayBuffer = new ArrayBuffer(2 ** 32 + 1); - assertEquals( - Deno.inspect(arrayBuffer), - `ArrayBuffer { - [Uint8Contents]: <00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... 4294967197 more bytes>, - byteLength: 4294967297 -}`, - ); - structuredClone(arrayBuffer, { transfer: [arrayBuffer] }); - assertEquals( - Deno.inspect(arrayBuffer), - "ArrayBuffer { (detached), byteLength: 0 }", - ); - - const sharedArrayBuffer = new SharedArrayBuffer(2 ** 32 + 1); - assertEquals( - Deno.inspect(sharedArrayBuffer), - `SharedArrayBuffer { - [Uint8Contents]: <00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... 4294967197 more bytes>, - byteLength: 4294967297 -}`, - ); -}); - -Deno.test(function inspectStringAbbreviation() { - const LONG_STRING = - "This is a really long string which will be abbreviated with ellipsis."; - const obj = { - str: LONG_STRING, - }; - const arr = [LONG_STRING]; - - assertEquals( - Deno.inspect(obj, { strAbbreviateSize: 10 }), - '{ str: "This is a "... 59 more characters }', - ); - - assertEquals( - Deno.inspect(arr, { strAbbreviateSize: 10 }), - '[ "This is a "... 59 more characters ]', - ); -}); - -Deno.test(async function inspectAggregateError() { - try { - await Promise.any([]); - } catch (err) { - assertEquals( - Deno.inspect(err).trimEnd(), - "AggregateError: All promises were rejected", - ); - } -}); - -Deno.test(function inspectWithPrototypePollution() { - const originalExec = RegExp.prototype.exec; - try { - RegExp.prototype.exec = () => { - throw Error(); - }; - Deno.inspect("foo"); - } finally { - RegExp.prototype.exec = originalExec; - } -}); - -Deno.test(function inspectPromiseLike() { - assertEquals( - Deno.inspect(Object.create(Promise.prototype)), - "Promise {}", - ); -}); - -Deno.test(function inspectorMethods() { - console.timeStamp("test"); - console.profile("test"); - console.profileEnd("test"); -}); - -Deno.test(function inspectQuotesOverride() { - assertEquals( - // @ts-ignore - 'quotes' is an internal option - Deno.inspect("foo", { quotes: ["'", '"', "`"] }), - "'foo'", - ); - assertEquals( - // @ts-ignore - 'quotes' is an internal option - Deno.inspect("'foo'", { quotes: ["'", '"', "`"] }), - `"'foo'"`, - ); -}); - -Deno.test(function inspectAnonymousFunctions() { - assertEquals(Deno.inspect(() => {}), "[Function (anonymous)]"); - assertEquals(Deno.inspect(function () {}), "[Function (anonymous)]"); - assertEquals(Deno.inspect(async () => {}), "[AsyncFunction (anonymous)]"); - assertEquals( - Deno.inspect(async function () {}), - "[AsyncFunction (anonymous)]", - ); - assertEquals( - Deno.inspect(function* () {}), - "[GeneratorFunction (anonymous)]", - ); - assertEquals( - Deno.inspect(async function* () {}), - "[AsyncGeneratorFunction (anonymous)]", - ); -}); - -Deno.test(function inspectBreakLengthOption() { - assertEquals( - Deno.inspect("123456789\n".repeat(3), { breakLength: 34 }), - `"123456789\\n123456789\\n123456789\\n"`, - ); - assertEquals( - Deno.inspect("123456789\n".repeat(3), { breakLength: 33 }), - `"123456789\\n" + - "123456789\\n" + - "123456789\\n"`, - ); -}); - -Deno.test(function inspectEscapeSequencesFalse() { - assertEquals( - Deno.inspect("foo\nbar", { escapeSequences: true }), - '"foo\\nbar"', - ); // default behavior - assertEquals( - Deno.inspect("foo\nbar", { escapeSequences: false }), - '"foo\nbar"', - ); -}); diff --git a/cli/tests/unit/copy_file_test.ts b/cli/tests/unit/copy_file_test.ts deleted file mode 100644 index ad467f510..000000000 --- a/cli/tests/unit/copy_file_test.ts +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals, assertRejects, assertThrows } from "./test_util.ts"; - -function readFileString(filename: string | URL): string { - const dataRead = Deno.readFileSync(filename); - const dec = new TextDecoder("utf-8"); - return dec.decode(dataRead); -} - -function writeFileString(filename: string | URL, s: string) { - const enc = new TextEncoder(); - const data = enc.encode(s); - Deno.writeFileSync(filename, data, { mode: 0o666 }); -} - -function assertSameContent( - filename1: string | URL, - filename2: string | URL, -) { - const data1 = Deno.readFileSync(filename1); - const data2 = Deno.readFileSync(filename2); - assertEquals(data1, data2); -} - -Deno.test( - { permissions: { read: true, write: true } }, - function copyFileSyncSuccess() { - const tempDir = Deno.makeTempDirSync(); - const fromFilename = tempDir + "/from.txt"; - const toFilename = tempDir + "/to.txt"; - writeFileString(fromFilename, "Hello world!"); - Deno.copyFileSync(fromFilename, toFilename); - // No change to original file - assertEquals(readFileString(fromFilename), "Hello world!"); - // Original == Dest - assertSameContent(fromFilename, toFilename); - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function copyFileSyncByUrl() { - const tempDir = Deno.makeTempDirSync(); - const fromUrl = new URL( - `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/from.txt`, - ); - const toUrl = new URL( - `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/to.txt`, - ); - writeFileString(fromUrl, "Hello world!"); - Deno.copyFileSync(fromUrl, toUrl); - // No change to original file - assertEquals(readFileString(fromUrl), "Hello world!"); - // Original == Dest - assertSameContent(fromUrl, toUrl); - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { write: true, read: true } }, - function copyFileSyncFailure() { - const tempDir = Deno.makeTempDirSync(); - const fromFilename = tempDir + "/from.txt"; - const toFilename = tempDir + "/to.txt"; - // We skip initial writing here, from.txt does not exist - assertThrows( - () => { - Deno.copyFileSync(fromFilename, toFilename); - }, - Deno.errors.NotFound, - `copy '${fromFilename}' -> '${toFilename}'`, - ); - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { write: true, read: false } }, - function copyFileSyncPerm1() { - assertThrows(() => { - Deno.copyFileSync("/from.txt", "/to.txt"); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { write: false, read: true } }, - function copyFileSyncPerm2() { - assertThrows(() => { - Deno.copyFileSync("/from.txt", "/to.txt"); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function copyFileSyncOverwrite() { - const tempDir = Deno.makeTempDirSync(); - const fromFilename = tempDir + "/from.txt"; - const toFilename = tempDir + "/to.txt"; - writeFileString(fromFilename, "Hello world!"); - // Make Dest exist and have different content - writeFileString(toFilename, "Goodbye!"); - Deno.copyFileSync(fromFilename, toFilename); - // No change to original file - assertEquals(readFileString(fromFilename), "Hello world!"); - // Original == Dest - assertSameContent(fromFilename, toFilename); - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function copyFileSuccess() { - const tempDir = Deno.makeTempDirSync(); - const fromFilename = tempDir + "/from.txt"; - const toFilename = tempDir + "/to.txt"; - writeFileString(fromFilename, "Hello world!"); - await Deno.copyFile(fromFilename, toFilename); - // No change to original file - assertEquals(readFileString(fromFilename), "Hello world!"); - // Original == Dest - assertSameContent(fromFilename, toFilename); - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function copyFileByUrl() { - const tempDir = Deno.makeTempDirSync(); - const fromUrl = new URL( - `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/from.txt`, - ); - const toUrl = new URL( - `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/to.txt`, - ); - writeFileString(fromUrl, "Hello world!"); - await Deno.copyFile(fromUrl, toUrl); - // No change to original file - assertEquals(readFileString(fromUrl), "Hello world!"); - // Original == Dest - assertSameContent(fromUrl, toUrl); - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function copyFileFailure() { - const tempDir = Deno.makeTempDirSync(); - const fromFilename = tempDir + "/from.txt"; - const toFilename = tempDir + "/to.txt"; - // We skip initial writing here, from.txt does not exist - await assertRejects( - async () => { - await Deno.copyFile(fromFilename, toFilename); - }, - Deno.errors.NotFound, - `copy '${fromFilename}' -> '${toFilename}'`, - ); - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function copyFileOverwrite() { - const tempDir = Deno.makeTempDirSync(); - const fromFilename = tempDir + "/from.txt"; - const toFilename = tempDir + "/to.txt"; - writeFileString(fromFilename, "Hello world!"); - // Make Dest exist and have different content - writeFileString(toFilename, "Goodbye!"); - await Deno.copyFile(fromFilename, toFilename); - // No change to original file - assertEquals(readFileString(fromFilename), "Hello world!"); - // Original == Dest - assertSameContent(fromFilename, toFilename); - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { read: false, write: true } }, - async function copyFilePerm1() { - await assertRejects(async () => { - await Deno.copyFile("/from.txt", "/to.txt"); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, write: false } }, - async function copyFilePerm2() { - await assertRejects(async () => { - await Deno.copyFile("/from.txt", "/to.txt"); - }, Deno.errors.PermissionDenied); - }, -); - -function copyFileSyncMode(content: string): void { - const tempDir = Deno.makeTempDirSync(); - const fromFilename = tempDir + "/from.txt"; - const toFilename = tempDir + "/to.txt"; - Deno.writeTextFileSync(fromFilename, content); - Deno.chmodSync(fromFilename, 0o100755); - - Deno.copyFileSync(fromFilename, toFilename); - const toStat = Deno.statSync(toFilename); - assertEquals(toStat.mode!, 0o100755); -} - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - function copyFileSyncChmod() { - // this Tests different optimization paths on MacOS: - // - // < 128 KB clonefile() w/ fallback to copyfile() - // > 128 KB - copyFileSyncMode("Hello world!"); - copyFileSyncMode("Hello world!".repeat(128 * 1024)); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function copyFileNulPath() { - const fromFilename = "from.txt\0"; - const toFilename = "to.txt\0"; - await assertRejects(async () => { - await Deno.copyFile(fromFilename, toFilename); - }, TypeError); - }, -); diff --git a/cli/tests/unit/cron_test.ts b/cli/tests/unit/cron_test.ts deleted file mode 100644 index 02573a898..000000000 --- a/cli/tests/unit/cron_test.ts +++ /dev/null @@ -1,460 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals, assertThrows } from "./test_util.ts"; - -// @ts-ignore This is not publicly typed namespace, but it's there for sure. -const { - formatToCronSchedule, - parseScheduleToString, - // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol -} = Deno[Deno.internal]; - -const sleep = (time: number) => new Promise((r) => setTimeout(r, time)); - -Deno.test(function noNameTest() { - assertThrows( - // @ts-ignore test - () => Deno.cron(), - TypeError, - "Deno.cron requires a unique name", - ); -}); - -Deno.test(function noSchedule() { - assertThrows( - // @ts-ignore test - () => Deno.cron("foo"), - TypeError, - "Deno.cron requires a valid schedule", - ); -}); - -Deno.test(function noHandler() { - assertThrows( - // @ts-ignore test - () => Deno.cron("foo", "*/1 * * * *"), - TypeError, - "Deno.cron requires a handler", - ); -}); - -Deno.test(function invalidNameTest() { - assertThrows( - () => Deno.cron("abc[]", "*/1 * * * *", () => {}), - TypeError, - "Invalid cron name", - ); - assertThrows( - () => Deno.cron("a**bc", "*/1 * * * *", () => {}), - TypeError, - "Invalid cron name", - ); - assertThrows( - () => Deno.cron("abc<>", "*/1 * * * *", () => {}), - TypeError, - "Invalid cron name", - ); - assertThrows( - () => Deno.cron(";']", "*/1 * * * *", () => {}), - TypeError, - "Invalid cron name", - ); - assertThrows( - () => - Deno.cron( - "0000000000000000000000000000000000000000000000000000000000000000000000", - "*/1 * * * *", - () => {}, - ), - TypeError, - "Cron name is too long", - ); -}); - -Deno.test(function invalidScheduleTest() { - assertThrows( - () => Deno.cron("abc", "bogus", () => {}), - TypeError, - "Invalid cron schedule", - ); - assertThrows( - () => Deno.cron("abc", "* * * * * *", () => {}), - TypeError, - "Invalid cron schedule", - ); - assertThrows( - () => Deno.cron("abc", "* * * *", () => {}), - TypeError, - "Invalid cron schedule", - ); - assertThrows( - () => Deno.cron("abc", "m * * * *", () => {}), - TypeError, - "Invalid cron schedule", - ); -}); - -Deno.test(function invalidBackoffScheduleTest() { - assertThrows( - () => - Deno.cron( - "abc", - "*/1 * * * *", - { backoffSchedule: [1, 1, 1, 1, 1, 1] }, - () => {}, - ), - TypeError, - "Invalid backoff schedule", - ); - assertThrows( - () => - Deno.cron("abc", "*/1 * * * *", { backoffSchedule: [3600001] }, () => {}), - TypeError, - "Invalid backoff schedule", - ); -}); - -Deno.test(async function tooManyCrons() { - const crons: Promise<void>[] = []; - const ac = new AbortController(); - for (let i = 0; i <= 100; i++) { - const c = Deno.cron( - `abc_${i}`, - "*/1 * * * *", - { signal: ac.signal }, - () => {}, - ); - crons.push(c); - } - - try { - assertThrows( - () => { - Deno.cron("next-cron", "*/1 * * * *", { signal: ac.signal }, () => {}); - }, - TypeError, - "Too many crons", - ); - } finally { - ac.abort(); - for (const c of crons) { - await c; - } - } -}); - -Deno.test(async function duplicateCrons() { - const ac = new AbortController(); - const c = Deno.cron("abc", "*/20 * * * *", { signal: ac.signal }, () => {}); - try { - assertThrows( - () => Deno.cron("abc", "*/20 * * * *", () => {}), - TypeError, - "Cron with this name already exists", - ); - } finally { - ac.abort(); - await c; - } -}); - -Deno.test(async function basicTest() { - Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "100"); - - let count = 0; - const { promise, resolve } = Promise.withResolvers<void>(); - const ac = new AbortController(); - const c = Deno.cron("abc", "*/20 * * * *", { signal: ac.signal }, () => { - count++; - if (count > 5) { - resolve(); - } - }); - try { - await promise; - } finally { - ac.abort(); - await c; - } -}); - -Deno.test(async function basicTestWithJsonFormatScheduleExpression() { - Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "100"); - - let count = 0; - const { promise, resolve } = Promise.withResolvers<void>(); - const ac = new AbortController(); - const c = Deno.cron( - "abc", - { minute: { every: 20 } }, - { signal: ac.signal }, - () => { - count++; - if (count > 5) { - resolve(); - } - }, - ); - try { - await promise; - } finally { - ac.abort(); - await c; - } -}); - -Deno.test(async function multipleCrons() { - Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "100"); - - let count0 = 0; - let count1 = 0; - const { promise: promise0, resolve: resolve0 } = Promise.withResolvers< - void - >(); - const { promise: promise1, resolve: resolve1 } = Promise.withResolvers< - void - >(); - const ac = new AbortController(); - const c0 = Deno.cron("abc", "*/20 * * * *", { signal: ac.signal }, () => { - count0++; - if (count0 > 5) { - resolve0(); - } - }); - const c1 = Deno.cron("xyz", "*/20 * * * *", { signal: ac.signal }, () => { - count1++; - if (count1 > 5) { - resolve1(); - } - }); - try { - await promise0; - await promise1; - } finally { - ac.abort(); - await c0; - await c1; - } -}); - -Deno.test(async function overlappingExecutions() { - Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "100"); - - let count = 0; - const { promise: promise0, resolve: resolve0 } = Promise.withResolvers< - void - >(); - const { promise: promise1, resolve: resolve1 } = Promise.withResolvers< - void - >(); - const ac = new AbortController(); - const c = Deno.cron( - "abc", - "*/20 * * * *", - { signal: ac.signal }, - async () => { - resolve0(); - count++; - await promise1; - }, - ); - try { - await promise0; - } finally { - await sleep(2000); - resolve1(); - ac.abort(); - await c; - } - assertEquals(count, 1); -}); - -Deno.test(async function retriesWithBackoffSchedule() { - Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "5000"); - - let count = 0; - const ac = new AbortController(); - const c = Deno.cron("abc", "*/20 * * * *", { - signal: ac.signal, - backoffSchedule: [10, 20], - }, async () => { - count += 1; - await sleep(10); - throw new TypeError("cron error"); - }); - try { - await sleep(6000); - } finally { - ac.abort(); - await c; - } - - // The cron should have executed 3 times (1st attempt and 2 retries). - assertEquals(count, 3); -}); - -Deno.test(async function retriesWithBackoffScheduleOldApi() { - Deno.env.set("DENO_CRON_TEST_SCHEDULE_OFFSET", "5000"); - - let count = 0; - const ac = new AbortController(); - const c = Deno.cron("abc2", "*/20 * * * *", { - signal: ac.signal, - backoffSchedule: [10, 20], - }, async () => { - count += 1; - await sleep(10); - throw new TypeError("cron error"); - }); - - try { - await sleep(6000); - } finally { - ac.abort(); - await c; - } - - // The cron should have executed 3 times (1st attempt and 2 retries). - assertEquals(count, 3); -}); - -Deno.test("formatToCronSchedule - undefined value", () => { - const result = formatToCronSchedule(); - assertEquals(result, "*"); -}); - -Deno.test("formatToCronSchedule - number value", () => { - const result = formatToCronSchedule(5); - assertEquals(result, "5"); -}); - -Deno.test("formatToCronSchedule - exact array value", () => { - const result = formatToCronSchedule({ exact: [1, 2, 3] }); - assertEquals(result, "1,2,3"); -}); - -Deno.test("formatToCronSchedule - exact number value", () => { - const result = formatToCronSchedule({ exact: 5 }); - assertEquals(result, "5"); -}); - -Deno.test("formatToCronSchedule - start, end, every values", () => { - const result = formatToCronSchedule({ start: 1, end: 10, every: 2 }); - assertEquals(result, "1-10/2"); -}); - -Deno.test("formatToCronSchedule - start, end values", () => { - const result = formatToCronSchedule({ start: 1, end: 10 }); - assertEquals(result, "1-10"); -}); - -Deno.test("formatToCronSchedule - start, every values", () => { - const result = formatToCronSchedule({ start: 1, every: 2 }); - assertEquals(result, "1/2"); -}); - -Deno.test("formatToCronSchedule - start value", () => { - const result = formatToCronSchedule({ start: 1 }); - assertEquals(result, "1/1"); -}); - -Deno.test("formatToCronSchedule - end, every values", () => { - assertThrows( - () => formatToCronSchedule({ end: 10, every: 2 }), - TypeError, - "Invalid cron schedule", - ); -}); - -Deno.test("Parse CronSchedule to string", () => { - const result = parseScheduleToString({ - minute: { exact: [1, 2, 3] }, - hour: { start: 1, end: 10, every: 2 }, - dayOfMonth: { exact: 5 }, - month: { start: 1, end: 10 }, - dayOfWeek: { start: 1, every: 2 }, - }); - assertEquals(result, "1,2,3 1-10/2 5 1-10 1/2"); -}); - -Deno.test("Parse schedule to string - string", () => { - const result = parseScheduleToString("* * * * *"); - assertEquals(result, "* * * * *"); -}); - -Deno.test("error on two handlers", () => { - assertThrows( - () => { - // @ts-ignore test - Deno.cron("abc", "* * * * *", () => {}, () => {}); - }, - TypeError, - "Deno.cron requires a single handler", - ); -}); - -Deno.test("Parse test", () => { - assertEquals( - parseScheduleToString({ - minute: 3, - }), - "3 * * * *", - ); - assertEquals( - parseScheduleToString({ - hour: { every: 2 }, - }), - "0 */2 * * *", - ); - assertEquals( - parseScheduleToString({ - dayOfMonth: { every: 10 }, - }), - "0 0 */10 * *", - ); - assertEquals( - parseScheduleToString({ - month: { every: 3 }, - }), - "0 0 1 */3 *", - ); - assertEquals( - parseScheduleToString({ - dayOfWeek: { every: 2 }, - }), - "0 0 * * */2", - ); - assertEquals( - parseScheduleToString({ - minute: 3, - hour: { every: 2 }, - }), - "3 */2 * * *", - ); - assertEquals( - parseScheduleToString({ - dayOfMonth: { start: 1, end: 10 }, - }), - "0 0 1-10 * *", - ); - assertEquals( - parseScheduleToString({ - minute: { every: 10 }, - dayOfMonth: { every: 5 }, - }), - "*/10 * */5 * *", - ); - assertEquals( - parseScheduleToString({ - hour: { every: 3 }, - month: { every: 2 }, - }), - "0 */3 * */2 *", - ); - assertEquals( - parseScheduleToString({ - minute: { every: 5 }, - month: { every: 2 }, - }), - "*/5 * * */2 *", - ); -}); diff --git a/cli/tests/unit/custom_event_test.ts b/cli/tests/unit/custom_event_test.ts deleted file mode 100644 index b72084eb2..000000000 --- a/cli/tests/unit/custom_event_test.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "./test_util.ts"; - -Deno.test(function customEventInitializedWithDetail() { - const type = "touchstart"; - const detail = { message: "hello" }; - const customEventInit = { - bubbles: true, - cancelable: true, - detail, - } as CustomEventInit; - const event = new CustomEvent(type, customEventInit); - - assertEquals(event.bubbles, true); - assertEquals(event.cancelable, true); - assertEquals(event.currentTarget, null); - assertEquals(event.detail, detail); - assertEquals(event.isTrusted, false); - assertEquals(event.target, null); - assertEquals(event.type, type); -}); - -Deno.test(function toStringShouldBeWebCompatibility() { - const type = "touchstart"; - const event = new CustomEvent(type, {}); - assertEquals(event.toString(), "[object CustomEvent]"); -}); diff --git a/cli/tests/unit/dir_test.ts b/cli/tests/unit/dir_test.ts deleted file mode 100644 index 4aaadfb12..000000000 --- a/cli/tests/unit/dir_test.ts +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals, assertThrows } from "./test_util.ts"; - -Deno.test({ permissions: { read: true } }, function dirCwdNotNull() { - assert(Deno.cwd() != null); -}); - -Deno.test( - { permissions: { read: true, write: true } }, - function dirCwdChdirSuccess() { - const initialdir = Deno.cwd(); - const path = Deno.makeTempDirSync(); - Deno.chdir(path); - const current = Deno.cwd(); - if (Deno.build.os === "darwin") { - assertEquals(current, "/private" + path); - } else { - assertEquals(current, path); - } - Deno.chdir(initialdir); - }, -); - -Deno.test({ permissions: { read: true, write: true } }, function dirCwdError() { - // excluding windows since it throws resource busy, while removeSync - if (["linux", "darwin"].includes(Deno.build.os)) { - const initialdir = Deno.cwd(); - const path = Deno.makeTempDirSync(); - Deno.chdir(path); - Deno.removeSync(path); - try { - assertThrows(() => { - Deno.cwd(); - }, Deno.errors.NotFound); - } finally { - Deno.chdir(initialdir); - } - } -}); - -Deno.test({ permissions: { read: false } }, function dirCwdPermError() { - assertThrows( - () => { - Deno.cwd(); - }, - Deno.errors.PermissionDenied, - "Requires read access to <CWD>, run again with the --allow-read flag", - ); -}); - -Deno.test( - { permissions: { read: true, write: true } }, - function dirChdirError() { - const path = Deno.makeTempDirSync() + "test"; - assertThrows( - () => { - Deno.chdir(path); - }, - Deno.errors.NotFound, - `chdir '${path}'`, - ); - }, -); diff --git a/cli/tests/unit/dom_exception_test.ts b/cli/tests/unit/dom_exception_test.ts deleted file mode 100644 index de335e105..000000000 --- a/cli/tests/unit/dom_exception_test.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { - assertEquals, - assertNotEquals, - assertStringIncludes, -} from "./test_util.ts"; - -Deno.test(function customInspectFunction() { - const exception = new DOMException("test"); - assertEquals(Deno.inspect(exception), exception.stack); - assertStringIncludes(Deno.inspect(DOMException.prototype), "DOMException"); -}); - -Deno.test(function nameToCodeMappingPrototypeAccess() { - const newCode = 100; - const objectPrototype = Object.prototype as unknown as { - pollution: number; - }; - objectPrototype.pollution = newCode; - assertNotEquals(newCode, new DOMException("test", "pollution").code); - Reflect.deleteProperty(objectPrototype, "pollution"); -}); diff --git a/cli/tests/unit/error_stack_test.ts b/cli/tests/unit/error_stack_test.ts deleted file mode 100644 index 7188b9f53..000000000 --- a/cli/tests/unit/error_stack_test.ts +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals, assertMatch } from "./test_util.ts"; - -Deno.test(function errorStackMessageLine() { - const e1 = new Error(); - e1.name = "Foo"; - e1.message = "bar"; - assertMatch(e1.stack!, /^Foo: bar\n/); - - const e2 = new Error(); - e2.name = ""; - e2.message = "bar"; - assertMatch(e2.stack!, /^bar\n/); - - const e3 = new Error(); - e3.name = "Foo"; - e3.message = ""; - assertMatch(e3.stack!, /^Foo\n/); - - const e4 = new Error(); - e4.name = ""; - e4.message = ""; - assertMatch(e4.stack!, /^\n/); - - const e5 = new Error(); - // deno-lint-ignore ban-ts-comment - // @ts-expect-error - e5.name = undefined; - // deno-lint-ignore ban-ts-comment - // @ts-expect-error - e5.message = undefined; - assertMatch(e5.stack!, /^Error\n/); - - const e6 = new Error(); - // deno-lint-ignore ban-ts-comment - // @ts-expect-error - e6.name = null; - // deno-lint-ignore ban-ts-comment - // @ts-expect-error - e6.message = null; - assertMatch(e6.stack!, /^null: null\n/); -}); - -Deno.test(function captureStackTrace() { - function foo() { - const error = new Error(); - const stack1 = error.stack!; - Error.captureStackTrace(error, foo); - const stack2 = error.stack!; - // stack2 should be stack1 without the first frame. - assertEquals(stack2, stack1.replace(/(?<=^[^\n]*\n)[^\n]*\n/, "")); - } - foo(); -}); diff --git a/cli/tests/unit/error_test.ts b/cli/tests/unit/error_test.ts deleted file mode 100644 index 9ba09ce0d..000000000 --- a/cli/tests/unit/error_test.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert, assertThrows, fail } from "./test_util.ts"; - -Deno.test("Errors work", () => { - assert(new Deno.errors.NotFound("msg") instanceof Error); - assert(new Deno.errors.PermissionDenied("msg") instanceof Error); - assert(new Deno.errors.ConnectionRefused("msg") instanceof Error); - assert(new Deno.errors.ConnectionReset("msg") instanceof Error); - assert(new Deno.errors.ConnectionAborted("msg") instanceof Error); - assert(new Deno.errors.NotConnected("msg") instanceof Error); - assert(new Deno.errors.AddrInUse("msg") instanceof Error); - assert(new Deno.errors.AddrNotAvailable("msg") instanceof Error); - assert(new Deno.errors.BrokenPipe("msg") instanceof Error); - assert(new Deno.errors.AlreadyExists("msg") instanceof Error); - assert(new Deno.errors.InvalidData("msg") instanceof Error); - assert(new Deno.errors.TimedOut("msg") instanceof Error); - assert(new Deno.errors.Interrupted("msg") instanceof Error); - assert(new Deno.errors.WouldBlock("msg") instanceof Error); - assert(new Deno.errors.WriteZero("msg") instanceof Error); - assert(new Deno.errors.UnexpectedEof("msg") instanceof Error); - assert(new Deno.errors.BadResource("msg") instanceof Error); - assert(new Deno.errors.Http("msg") instanceof Error); - assert(new Deno.errors.Busy("msg") instanceof Error); - assert(new Deno.errors.NotSupported("msg") instanceof Error); -}); - -Deno.test("Errors have some tamper resistance", () => { - // deno-lint-ignore no-explicit-any - (Object.prototype as any).get = () => {}; - assertThrows(() => fail("test error"), Error, "test error"); - // deno-lint-ignore no-explicit-any - delete (Object.prototype as any).get; -}); diff --git a/cli/tests/unit/esnext_test.ts b/cli/tests/unit/esnext_test.ts deleted file mode 100644 index 6b2334f42..000000000 --- a/cli/tests/unit/esnext_test.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "./test_util.ts"; - -// TODO(@kitsonk) remove when we are no longer patching TypeScript to have -// these types available. - -Deno.test(function typeCheckingEsNextArrayString() { - const b = ["a", "b", "c", "d", "e", "f"]; - assertEquals(b.findLast((val) => typeof val === "string"), "f"); - assertEquals(b.findLastIndex((val) => typeof val === "string"), 5); -}); - -Deno.test(function intlListFormat() { - const formatter = new Intl.ListFormat("en", { - style: "long", - type: "conjunction", - }); - assertEquals( - formatter.format(["red", "green", "blue"]), - "red, green, and blue", - ); - - const formatter2 = new Intl.ListFormat("en", { - style: "short", - type: "disjunction", - }); - assertEquals(formatter2.formatToParts(["Rust", "golang"]), [ - { type: "element", value: "Rust" }, - { type: "literal", value: " or " }, - { type: "element", value: "golang" }, - ]); - - // Works with iterables as well - assertEquals( - formatter.format(new Set(["red", "green", "blue"])), - "red, green, and blue", - ); - assertEquals(formatter2.formatToParts(new Set(["Rust", "golang"])), [ - { type: "element", value: "Rust" }, - { type: "literal", value: " or " }, - { type: "element", value: "golang" }, - ]); -}); diff --git a/cli/tests/unit/event_target_test.ts b/cli/tests/unit/event_target_test.ts deleted file mode 100644 index b084eaf90..000000000 --- a/cli/tests/unit/event_target_test.ts +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -// deno-lint-ignore-file no-window-prefix -import { assertEquals, assertThrows } from "./test_util.ts"; - -Deno.test(function addEventListenerTest() { - const document = new EventTarget(); - - assertEquals(document.addEventListener("x", null, false), undefined); - assertEquals(document.addEventListener("x", null, true), undefined); - assertEquals(document.addEventListener("x", null), undefined); -}); - -Deno.test(function constructedEventTargetCanBeUsedAsExpected() { - const target = new EventTarget(); - const event = new Event("foo", { bubbles: true, cancelable: false }); - let callCount = 0; - - const listener = (e: Event) => { - assertEquals(e, event); - ++callCount; - }; - - target.addEventListener("foo", listener); - - target.dispatchEvent(event); - assertEquals(callCount, 1); - - target.dispatchEvent(event); - assertEquals(callCount, 2); - - target.removeEventListener("foo", listener); - target.dispatchEvent(event); - assertEquals(callCount, 2); -}); - -Deno.test(function anEventTargetCanBeSubclassed() { - class NicerEventTarget extends EventTarget { - on( - type: string, - callback: ((e: Event) => void) | null, - options?: AddEventListenerOptions, - ) { - this.addEventListener(type, callback, options); - } - - off( - type: string, - callback: ((e: Event) => void) | null, - options?: EventListenerOptions, - ) { - this.removeEventListener(type, callback, options); - } - } - - const target = new NicerEventTarget(); - new Event("foo", { bubbles: true, cancelable: false }); - let callCount = 0; - - const listener = () => { - ++callCount; - }; - - target.on("foo", listener); - assertEquals(callCount, 0); - - target.off("foo", listener); - assertEquals(callCount, 0); -}); - -Deno.test(function removingNullEventListenerShouldSucceed() { - const document = new EventTarget(); - assertEquals(document.removeEventListener("x", null, false), undefined); - assertEquals(document.removeEventListener("x", null, true), undefined); - assertEquals(document.removeEventListener("x", null), undefined); -}); - -Deno.test(function constructedEventTargetUseObjectPrototype() { - const target = new EventTarget(); - const event = new Event("toString", { bubbles: true, cancelable: false }); - let callCount = 0; - - const listener = (e: Event) => { - assertEquals(e, event); - ++callCount; - }; - - target.addEventListener("toString", listener); - - target.dispatchEvent(event); - assertEquals(callCount, 1); - - target.dispatchEvent(event); - assertEquals(callCount, 2); - - target.removeEventListener("toString", listener); - target.dispatchEvent(event); - assertEquals(callCount, 2); -}); - -Deno.test(function toStringShouldBeWebCompatible() { - const target = new EventTarget(); - assertEquals(target.toString(), "[object EventTarget]"); -}); - -Deno.test(function dispatchEventShouldNotThrowError() { - let hasThrown = false; - - try { - const target = new EventTarget(); - const event = new Event("hasOwnProperty", { - bubbles: true, - cancelable: false, - }); - const listener = () => {}; - target.addEventListener("hasOwnProperty", listener); - target.dispatchEvent(event); - } catch { - hasThrown = true; - } - - assertEquals(hasThrown, false); -}); - -Deno.test(function eventTargetThisShouldDefaultToWindow() { - const { - addEventListener, - dispatchEvent, - removeEventListener, - } = EventTarget.prototype; - let n = 1; - const event = new Event("hello"); - const listener = () => { - n = 2; - }; - - addEventListener("hello", listener); - window.dispatchEvent(event); - assertEquals(n, 2); - n = 1; - removeEventListener("hello", listener); - window.dispatchEvent(event); - assertEquals(n, 1); - - window.addEventListener("hello", listener); - dispatchEvent(event); - assertEquals(n, 2); - n = 1; - window.removeEventListener("hello", listener); - dispatchEvent(event); - assertEquals(n, 1); -}); - -Deno.test(function eventTargetShouldAcceptEventListenerObject() { - const target = new EventTarget(); - const event = new Event("foo", { bubbles: true, cancelable: false }); - let callCount = 0; - - const listener = { - handleEvent(e: Event) { - assertEquals(e, event); - ++callCount; - }, - }; - - target.addEventListener("foo", listener); - - target.dispatchEvent(event); - assertEquals(callCount, 1); - - target.dispatchEvent(event); - assertEquals(callCount, 2); - - target.removeEventListener("foo", listener); - target.dispatchEvent(event); - assertEquals(callCount, 2); -}); - -Deno.test(function eventTargetShouldAcceptAsyncFunction() { - const target = new EventTarget(); - const event = new Event("foo", { bubbles: true, cancelable: false }); - let callCount = 0; - - const listener = (e: Event) => { - assertEquals(e, event); - ++callCount; - }; - - target.addEventListener("foo", listener); - - target.dispatchEvent(event); - assertEquals(callCount, 1); - - target.dispatchEvent(event); - assertEquals(callCount, 2); - - target.removeEventListener("foo", listener); - target.dispatchEvent(event); - assertEquals(callCount, 2); -}); - -Deno.test( - function eventTargetShouldAcceptAsyncFunctionForEventListenerObject() { - const target = new EventTarget(); - const event = new Event("foo", { bubbles: true, cancelable: false }); - let callCount = 0; - - const listener = { - handleEvent(e: Event) { - assertEquals(e, event); - ++callCount; - }, - }; - - target.addEventListener("foo", listener); - - target.dispatchEvent(event); - assertEquals(callCount, 1); - - target.dispatchEvent(event); - assertEquals(callCount, 2); - - target.removeEventListener("foo", listener); - target.dispatchEvent(event); - assertEquals(callCount, 2); - }, -); -Deno.test(function eventTargetDispatchShouldSetTargetNoListener() { - const target = new EventTarget(); - const event = new Event("foo"); - assertEquals(event.target, null); - target.dispatchEvent(event); - assertEquals(event.target, target); -}); - -Deno.test(function eventTargetDispatchShouldSetTargetInListener() { - const target = new EventTarget(); - const event = new Event("foo"); - assertEquals(event.target, null); - let called = false; - target.addEventListener("foo", (e) => { - assertEquals(e.target, target); - called = true; - }); - target.dispatchEvent(event); - assertEquals(called, true); -}); - -Deno.test(function eventTargetDispatchShouldFireCurrentListenersOnly() { - const target = new EventTarget(); - const event = new Event("foo"); - let callCount = 0; - target.addEventListener("foo", () => { - ++callCount; - target.addEventListener("foo", () => { - ++callCount; - }); - }); - target.dispatchEvent(event); - assertEquals(callCount, 1); -}); - -Deno.test(function eventTargetAddEventListenerGlobalAbort() { - return new Promise((resolve) => { - const c = new AbortController(); - - c.signal.addEventListener("abort", () => resolve()); - addEventListener("test", () => {}, { signal: c.signal }); - c.abort(); - }); -}); - -Deno.test(function eventTargetBrandChecking() { - const self = {}; - - assertThrows( - () => { - EventTarget.prototype.addEventListener.call(self, "test", null); - }, - TypeError, - ); - - assertThrows( - () => { - EventTarget.prototype.removeEventListener.call(self, "test", null); - }, - TypeError, - ); - - assertThrows( - () => { - EventTarget.prototype.dispatchEvent.call(self, new Event("test")); - }, - TypeError, - ); -}); diff --git a/cli/tests/unit/event_test.ts b/cli/tests/unit/event_test.ts deleted file mode 100644 index c82873cf6..000000000 --- a/cli/tests/unit/event_test.ts +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals, assertStringIncludes } from "./test_util.ts"; - -Deno.test(function eventInitializedWithType() { - const type = "click"; - const event = new Event(type); - - assertEquals(event.isTrusted, false); - assertEquals(event.target, null); - assertEquals(event.currentTarget, null); - assertEquals(event.type, "click"); - assertEquals(event.bubbles, false); - assertEquals(event.cancelable, false); -}); - -Deno.test(function eventInitializedWithTypeAndDict() { - const init = "submit"; - const eventInit = { bubbles: true, cancelable: true } as EventInit; - const event = new Event(init, eventInit); - - assertEquals(event.isTrusted, false); - assertEquals(event.target, null); - assertEquals(event.currentTarget, null); - assertEquals(event.type, "submit"); - assertEquals(event.bubbles, true); - assertEquals(event.cancelable, true); -}); - -Deno.test(function eventComposedPathSuccess() { - const type = "click"; - const event = new Event(type); - const composedPath = event.composedPath(); - - assertEquals(composedPath, []); -}); - -Deno.test(function eventStopPropagationSuccess() { - const type = "click"; - const event = new Event(type); - - assertEquals(event.cancelBubble, false); - event.stopPropagation(); - assertEquals(event.cancelBubble, true); -}); - -Deno.test(function eventStopImmediatePropagationSuccess() { - const type = "click"; - const event = new Event(type); - - assertEquals(event.cancelBubble, false); - event.stopImmediatePropagation(); - assertEquals(event.cancelBubble, true); -}); - -Deno.test(function eventPreventDefaultSuccess() { - const type = "click"; - const event = new Event(type); - - assertEquals(event.defaultPrevented, false); - event.preventDefault(); - assertEquals(event.defaultPrevented, false); - - const eventInit = { bubbles: true, cancelable: true } as EventInit; - const cancelableEvent = new Event(type, eventInit); - assertEquals(cancelableEvent.defaultPrevented, false); - cancelableEvent.preventDefault(); - assertEquals(cancelableEvent.defaultPrevented, true); -}); - -Deno.test(function eventInitializedWithNonStringType() { - // deno-lint-ignore no-explicit-any - const type: any = undefined; - const event = new Event(type); - - assertEquals(event.isTrusted, false); - assertEquals(event.target, null); - assertEquals(event.currentTarget, null); - assertEquals(event.type, "undefined"); - assertEquals(event.bubbles, false); - assertEquals(event.cancelable, false); -}); - -Deno.test(function eventInspectOutput() { - // deno-lint-ignore no-explicit-any - const cases: Array<[any, (event: any) => string]> = [ - [ - new Event("test"), - (event: Event) => - `Event {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "test"\n}`, - ], - [ - new ErrorEvent("error"), - (event: Event) => - `ErrorEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "error",\n message: "",\n filename: "",\n lineno: 0,\n colno: 0,\n error: undefined\n}`, - ], - [ - new CloseEvent("close"), - (event: Event) => - `CloseEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "close",\n wasClean: false,\n code: 0,\n reason: ""\n}`, - ], - [ - new CustomEvent("custom"), - (event: Event) => - `CustomEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "custom",\n detail: undefined\n}`, - ], - [ - new ProgressEvent("progress"), - (event: Event) => - `ProgressEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "progress",\n lengthComputable: false,\n loaded: 0,\n total: 0\n}`, - ], - ]; - - for (const [event, outputProvider] of cases) { - assertEquals(Deno.inspect(event), outputProvider(event)); - } -}); - -Deno.test(function inspectEvent() { - // has a customInspect implementation that previously would throw on a getter - assertEquals( - Deno.inspect(Event.prototype), - `Event { - bubbles: [Getter], - cancelable: [Getter], - composed: [Getter], - currentTarget: [Getter], - defaultPrevented: [Getter], - eventPhase: [Getter], - srcElement: [Getter/Setter], - target: [Getter], - returnValue: [Getter/Setter], - timeStamp: [Getter], - type: [Getter] -}`, - ); - - // ensure this still works - assertStringIncludes( - Deno.inspect(new Event("test")), - // check a substring because one property is a timestamp - `Event {\n bubbles: false,\n cancelable: false,`, - ); -}); diff --git a/cli/tests/unit/fetch_test.ts b/cli/tests/unit/fetch_test.ts deleted file mode 100644 index 84e94de0d..000000000 --- a/cli/tests/unit/fetch_test.ts +++ /dev/null @@ -1,2071 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertRejects, - assertThrows, - delay, - fail, - unimplemented, -} from "./test_util.ts"; -import { Buffer } from "@test_util/std/io/buffer.ts"; - -const listenPort = 4506; - -Deno.test( - { permissions: { net: true } }, - async function fetchRequiresOneArgument() { - await assertRejects( - fetch as unknown as () => Promise<void>, - TypeError, - ); - }, -); - -Deno.test({ permissions: { net: true } }, async function fetchProtocolError() { - await assertRejects( - async () => { - await fetch("ftp://localhost:21/a/file"); - }, - TypeError, - "not supported", - ); -}); - -function findClosedPortInRange( - minPort: number, - maxPort: number, -): number | never { - let port = minPort; - - // If we hit the return statement of this loop - // that means that we did not throw an - // AddrInUse error when we executed Deno.listen. - while (port < maxPort) { - try { - const listener = Deno.listen({ port }); - listener.close(); - return port; - } catch (_e) { - port++; - } - } - - unimplemented( - `No available ports between ${minPort} and ${maxPort} to test fetch`, - ); -} - -Deno.test( - // TODO(bartlomieju): reenable this test - // https://github.com/denoland/deno/issues/18350 - { ignore: Deno.build.os === "windows", permissions: { net: true } }, - async function fetchConnectionError() { - const port = findClosedPortInRange(4000, 9999); - await assertRejects( - async () => { - await fetch(`http://localhost:${port}`); - }, - TypeError, - "error trying to connect", - ); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchDnsError() { - await assertRejects( - async () => { - await fetch("http://nil/"); - }, - TypeError, - "error trying to connect", - ); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchInvalidUriError() { - await assertRejects( - async () => { - await fetch("http://<invalid>/"); - }, - TypeError, - ); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchMalformedUriError() { - await assertRejects( - async () => { - const url = new URL("http://{{google/"); - await fetch(url); - }, - TypeError, - ); - }, -); - -Deno.test({ permissions: { net: true } }, async function fetchJsonSuccess() { - const response = await fetch("http://localhost:4545/assets/fixture.json"); - const json = await response.json(); - assertEquals(json.name, "deno"); -}); - -Deno.test({ permissions: { net: false } }, async function fetchPerm() { - await assertRejects(async () => { - await fetch("http://localhost:4545/assets/fixture.json"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { net: true } }, async function fetchUrl() { - const response = await fetch("http://localhost:4545/assets/fixture.json"); - assertEquals(response.url, "http://localhost:4545/assets/fixture.json"); - const _json = await response.json(); -}); - -Deno.test({ permissions: { net: true } }, async function fetchURL() { - const response = await fetch( - new URL("http://localhost:4545/assets/fixture.json"), - ); - assertEquals(response.url, "http://localhost:4545/assets/fixture.json"); - const _json = await response.json(); -}); - -Deno.test({ permissions: { net: true } }, async function fetchHeaders() { - const response = await fetch("http://localhost:4545/assets/fixture.json"); - const headers = response.headers; - assertEquals(headers.get("Content-Type"), "application/json"); - const _json = await response.json(); -}); - -Deno.test({ permissions: { net: true } }, async function fetchBlob() { - const response = await fetch("http://localhost:4545/assets/fixture.json"); - const headers = response.headers; - const blob = await response.blob(); - assertEquals(blob.type, headers.get("Content-Type")); - assertEquals(blob.size, Number(headers.get("Content-Length"))); -}); - -Deno.test( - { permissions: { net: true } }, - async function fetchBodyUsedReader() { - const response = await fetch( - "http://localhost:4545/assets/fixture.json", - ); - assert(response.body !== null); - - const reader = response.body.getReader(); - // Getting a reader should lock the stream but does not consume the body - // so bodyUsed should not be true - assertEquals(response.bodyUsed, false); - reader.releaseLock(); - await response.json(); - assertEquals(response.bodyUsed, true); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchBodyUsedCancelStream() { - const response = await fetch( - "http://localhost:4545/assets/fixture.json", - ); - assert(response.body !== null); - - assertEquals(response.bodyUsed, false); - const promise = response.body.cancel(); - assertEquals(response.bodyUsed, true); - await promise; - }, -); - -Deno.test({ permissions: { net: true } }, async function fetchAsyncIterator() { - const response = await fetch("http://localhost:4545/assets/fixture.json"); - const headers = response.headers; - - assert(response.body !== null); - let total = 0; - for await (const chunk of response.body) { - assert(chunk instanceof Uint8Array); - total += chunk.length; - } - - assertEquals(total, Number(headers.get("Content-Length"))); -}); - -Deno.test({ permissions: { net: true } }, async function fetchBodyReader() { - const response = await fetch("http://localhost:4545/assets/fixture.json"); - const headers = response.headers; - assert(response.body !== null); - const reader = response.body.getReader(); - let total = 0; - while (true) { - const { done, value } = await reader.read(); - if (done) break; - assert(value); - assert(value instanceof Uint8Array); - total += value.length; - } - - assertEquals(total, Number(headers.get("Content-Length"))); -}); - -Deno.test( - { permissions: { net: true } }, - async function fetchBodyReaderBigBody() { - const data = "a".repeat(10 << 10); // 10mb - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: data, - }); - assert(response.body !== null); - const reader = await response.body.getReader(); - let total = 0; - while (true) { - const { done, value } = await reader.read(); - if (done) break; - assert(value); - total += value.length; - } - - assertEquals(total, data.length); - }, -); - -Deno.test({ permissions: { net: true } }, async function responseClone() { - const response = await fetch("http://localhost:4545/assets/fixture.json"); - const response1 = response.clone(); - assert(response !== response1); - assertEquals(response.status, response1.status); - assertEquals(response.statusText, response1.statusText); - const u8a = new Uint8Array(await response.arrayBuffer()); - const u8a1 = new Uint8Array(await response1.arrayBuffer()); - for (let i = 0; i < u8a.byteLength; i++) { - assertEquals(u8a[i], u8a1[i]); - } -}); - -Deno.test( - { permissions: { net: true } }, - async function fetchMultipartFormDataSuccess() { - const response = await fetch( - "http://localhost:4545/multipart_form_data.txt", - ); - const formData = await response.formData(); - assert(formData.has("field_1")); - assertEquals(formData.get("field_1")!.toString(), "value_1 \r\n"); - assert(formData.has("field_2")); - const file = formData.get("field_2") as File; - assertEquals(file.name, "file.js"); - - assertEquals(await file.text(), `console.log("Hi")`); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchMultipartFormBadContentType() { - const response = await fetch( - "http://localhost:4545/multipart_form_bad_content_type", - ); - assert(response.body !== null); - - await assertRejects( - async () => { - await response.formData(); - }, - TypeError, - "Body can not be decoded as form data", - ); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchURLEncodedFormDataSuccess() { - const response = await fetch( - "http://localhost:4545/subdir/form_urlencoded.txt", - ); - const formData = await response.formData(); - assert(formData.has("field_1")); - assertEquals(formData.get("field_1")!.toString(), "Hi"); - assert(formData.has("field_2")); - assertEquals(formData.get("field_2")!.toString(), "<Deno>"); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchInitFormDataBinaryFileBody() { - // Some random bytes - // deno-fmt-ignore - const binaryFile = new Uint8Array([108,2,0,0,145,22,162,61,157,227,166,77,138,75,180,56,119,188,177,183]); - const response = await fetch("http://localhost:4545/echo_multipart_file", { - method: "POST", - body: binaryFile, - }); - const resultForm = await response.formData(); - const resultFile = resultForm.get("file") as File; - - assertEquals(resultFile.type, "application/octet-stream"); - assertEquals(resultFile.name, "file.bin"); - assertEquals(new Uint8Array(await resultFile.arrayBuffer()), binaryFile); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchInitFormDataMultipleFilesBody() { - const files = [ - { - // deno-fmt-ignore - content: new Uint8Array([137,80,78,71,13,10,26,10, 137, 1, 25]), - type: "image/png", - name: "image", - fileName: "some-image.png", - }, - { - // deno-fmt-ignore - content: new Uint8Array([108,2,0,0,145,22,162,61,157,227,166,77,138,75,180,56,119,188,177,183]), - name: "file", - fileName: "file.bin", - expectedType: "application/octet-stream", - }, - { - content: new TextEncoder().encode("deno land"), - type: "text/plain", - name: "text", - fileName: "deno.txt", - }, - ]; - const form = new FormData(); - form.append("field", "value"); - for (const file of files) { - form.append( - file.name, - new Blob([file.content], { type: file.type }), - file.fileName, - ); - } - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: form, - }); - const resultForm = await response.formData(); - assertEquals(form.get("field"), resultForm.get("field")); - for (const file of files) { - const inputFile = form.get(file.name) as File; - const resultFile = resultForm.get(file.name) as File; - assertEquals(inputFile.size, resultFile.size); - assertEquals(inputFile.name, resultFile.name); - assertEquals(file.expectedType || file.type, resultFile.type); - assertEquals( - new Uint8Array(await resultFile.arrayBuffer()), - file.content, - ); - } - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchWithRedirection() { - const response = await fetch("http://localhost:4546/assets/hello.txt"); - assertEquals(response.status, 200); - assertEquals(response.statusText, "OK"); - assertEquals(response.url, "http://localhost:4545/assets/hello.txt"); - const body = await response.text(); - assert(body.includes("Hello world!")); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchWithRelativeRedirection() { - const response = await fetch( - "http://localhost:4545/run/001_hello.js", - ); - assertEquals(response.status, 200); - assertEquals(response.statusText, "OK"); - const body = await response.text(); - assert(body.includes("Hello")); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchWithRelativeRedirectionUrl() { - const cases = [ - ["end", "http://localhost:4550/a/b/end"], - ["/end", "http://localhost:4550/end"], - ]; - for (const [loc, redUrl] of cases) { - const response = await fetch("http://localhost:4550/a/b/c", { - headers: new Headers([["x-location", loc]]), - }); - assertEquals(response.url, redUrl); - assertEquals(response.redirected, true); - assertEquals(response.status, 404); - assertEquals(await response.text(), ""); - } - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchWithInfRedirection() { - await assertRejects( - () => fetch("http://localhost:4549"), - TypeError, - "redirect", - ); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchInitStringBody() { - const data = "Hello World"; - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: data, - }); - const text = await response.text(); - assertEquals(text, data); - assert(response.headers.get("content-type")!.startsWith("text/plain")); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchRequestInitStringBody() { - const data = "Hello World"; - const req = new Request("http://localhost:4545/echo_server", { - method: "POST", - body: data, - }); - const response = await fetch(req); - const text = await response.text(); - assertEquals(text, data); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchSeparateInit() { - // related to: https://github.com/denoland/deno/issues/10396 - const req = new Request("http://localhost:4545/run/001_hello.js"); - const init = { - method: "GET", - }; - req.headers.set("foo", "bar"); - const res = await fetch(req, init); - assertEquals(res.status, 200); - await res.text(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchInitTypedArrayBody() { - const data = "Hello World"; - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: new TextEncoder().encode(data), - }); - const text = await response.text(); - assertEquals(text, data); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchInitArrayBufferBody() { - const data = "Hello World"; - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: new TextEncoder().encode(data).buffer, - }); - const text = await response.text(); - assertEquals(text, data); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchInitURLSearchParamsBody() { - const data = "param1=value1¶m2=value2"; - const params = new URLSearchParams(data); - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: params, - }); - const text = await response.text(); - assertEquals(text, data); - assert( - response.headers - .get("content-type")! - .startsWith("application/x-www-form-urlencoded"), - ); - }, -); - -Deno.test({ permissions: { net: true } }, async function fetchInitBlobBody() { - const data = "const a = 1 🦕"; - const blob = new Blob([data], { - type: "text/javascript", - }); - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: blob, - }); - const text = await response.text(); - assertEquals(text, data); - assert(response.headers.get("content-type")!.startsWith("text/javascript")); -}); - -Deno.test( - { permissions: { net: true } }, - async function fetchInitFormDataBody() { - const form = new FormData(); - form.append("field", "value"); - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: form, - }); - const resultForm = await response.formData(); - assertEquals(form.get("field"), resultForm.get("field")); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchInitFormDataBlobFilenameBody() { - const form = new FormData(); - form.append("field", "value"); - form.append( - "file", - new Blob([new TextEncoder().encode("deno")]), - "file name", - ); - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: form, - }); - const resultForm = await response.formData(); - assertEquals(form.get("field"), resultForm.get("field")); - const file = resultForm.get("file"); - assert(file instanceof File); - assertEquals(file.name, "file name"); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchInitFormDataFileFilenameBody() { - const form = new FormData(); - form.append("field", "value"); - form.append( - "file", - new File([new Blob([new TextEncoder().encode("deno")])], "file name"), - ); - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: form, - }); - const resultForm = await response.formData(); - assertEquals(form.get("field"), resultForm.get("field")); - const file = resultForm.get("file"); - assert(file instanceof File); - assertEquals(file.name, "file name"); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchInitFormDataTextFileBody() { - const fileContent = "deno land"; - const form = new FormData(); - form.append("field", "value"); - form.append( - "file", - new Blob([new TextEncoder().encode(fileContent)], { - type: "text/plain", - }), - "deno.txt", - ); - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: form, - }); - const resultForm = await response.formData(); - assertEquals(form.get("field"), resultForm.get("field")); - - const file = form.get("file") as File; - const resultFile = resultForm.get("file") as File; - - assertEquals(file.size, resultFile.size); - assertEquals(file.name, resultFile.name); - assertEquals(file.type, resultFile.type); - assertEquals(await file.text(), await resultFile.text()); - }, -); - -Deno.test({ permissions: { net: true } }, async function fetchUserAgent() { - const data = "Hello World"; - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: new TextEncoder().encode(data), - }); - assertEquals(response.headers.get("user-agent"), `Deno/${Deno.version.deno}`); - await response.text(); -}); - -function bufferServer(addr: string): Promise<Buffer> { - const [hostname, port] = addr.split(":"); - const listener = Deno.listen({ - hostname, - port: Number(port), - }) as Deno.Listener; - return listener.accept().then(async (conn: Deno.Conn) => { - const buf = new Buffer(); - const p1 = buf.readFrom(conn); - const p2 = conn.write( - new TextEncoder().encode( - "HTTP/1.0 404 Not Found\r\nContent-Length: 2\r\n\r\nNF", - ), - ); - // Wait for both an EOF on the read side of the socket and for the write to - // complete before closing it. Due to keep-alive, the EOF won't be sent - // until the Connection close (HTTP/1.0) response, so readFrom() can't - // proceed write. Conversely, if readFrom() is async, waiting for the - // write() to complete is not a guarantee that we've read the incoming - // request. - await Promise.all([p1, p2]); - conn.close(); - listener.close(); - return buf; - }); -} - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchRequest() { - const addr = `127.0.0.1:${listenPort}`; - const bufPromise = bufferServer(addr); - const response = await fetch(`http://${addr}/blah`, { - method: "POST", - headers: [ - ["Hello", "World"], - ["Foo", "Bar"], - ], - }); - await response.arrayBuffer(); - assertEquals(response.status, 404); - assertEquals(response.headers.get("Content-Length"), "2"); - - const actual = new TextDecoder().decode((await bufPromise).bytes()); - const expected = [ - "POST /blah HTTP/1.1\r\n", - "content-length: 0\r\n", - "hello: World\r\n", - "foo: Bar\r\n", - "accept: */*\r\n", - "accept-language: *\r\n", - `user-agent: Deno/${Deno.version.deno}\r\n`, - "accept-encoding: gzip, br\r\n", - `host: ${addr}\r\n\r\n`, - ].join(""); - assertEquals(actual, expected); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchRequestAcceptHeaders() { - const addr = `127.0.0.1:${listenPort}`; - const bufPromise = bufferServer(addr); - const response = await fetch(`http://${addr}/blah`, { - method: "POST", - headers: [ - ["Accept", "text/html"], - ["Accept-Language", "en-US"], - ], - }); - await response.arrayBuffer(); - assertEquals(response.status, 404); - assertEquals(response.headers.get("Content-Length"), "2"); - - const actual = new TextDecoder().decode((await bufPromise).bytes()); - const expected = [ - "POST /blah HTTP/1.1\r\n", - "content-length: 0\r\n", - "accept: text/html\r\n", - "accept-language: en-US\r\n", - `user-agent: Deno/${Deno.version.deno}\r\n`, - "accept-encoding: gzip, br\r\n", - `host: ${addr}\r\n\r\n`, - ].join(""); - assertEquals(actual, expected); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchPostBodyString() { - const addr = `127.0.0.1:${listenPort}`; - const bufPromise = bufferServer(addr); - const body = "hello world"; - const response = await fetch(`http://${addr}/blah`, { - method: "POST", - headers: [ - ["Hello", "World"], - ["Foo", "Bar"], - ], - body, - }); - await response.arrayBuffer(); - assertEquals(response.status, 404); - assertEquals(response.headers.get("Content-Length"), "2"); - - const actual = new TextDecoder().decode((await bufPromise).bytes()); - const expected = [ - "POST /blah HTTP/1.1\r\n", - "hello: World\r\n", - "foo: Bar\r\n", - "content-type: text/plain;charset=UTF-8\r\n", - "accept: */*\r\n", - "accept-language: *\r\n", - `user-agent: Deno/${Deno.version.deno}\r\n`, - "accept-encoding: gzip, br\r\n", - `host: ${addr}\r\n`, - `content-length: ${body.length}\r\n\r\n`, - body, - ].join(""); - assertEquals(actual, expected); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchPostBodyTypedArray() { - const addr = `127.0.0.1:${listenPort}`; - const bufPromise = bufferServer(addr); - const bodyStr = "hello world"; - const body = new TextEncoder().encode(bodyStr); - const response = await fetch(`http://${addr}/blah`, { - method: "POST", - headers: [ - ["Hello", "World"], - ["Foo", "Bar"], - ], - body, - }); - await response.arrayBuffer(); - assertEquals(response.status, 404); - assertEquals(response.headers.get("Content-Length"), "2"); - - const actual = new TextDecoder().decode((await bufPromise).bytes()); - const expected = [ - "POST /blah HTTP/1.1\r\n", - "hello: World\r\n", - "foo: Bar\r\n", - "accept: */*\r\n", - "accept-language: *\r\n", - `user-agent: Deno/${Deno.version.deno}\r\n`, - "accept-encoding: gzip, br\r\n", - `host: ${addr}\r\n`, - `content-length: ${body.byteLength}\r\n\r\n`, - bodyStr, - ].join(""); - assertEquals(actual, expected); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchUserSetContentLength() { - const addr = `127.0.0.1:${listenPort}`; - const bufPromise = bufferServer(addr); - const response = await fetch(`http://${addr}/blah`, { - method: "POST", - headers: [ - ["Content-Length", "10"], - ], - }); - await response.arrayBuffer(); - assertEquals(response.status, 404); - assertEquals(response.headers.get("Content-Length"), "2"); - - const actual = new TextDecoder().decode((await bufPromise).bytes()); - const expected = [ - "POST /blah HTTP/1.1\r\n", - "content-length: 0\r\n", - "accept: */*\r\n", - "accept-language: *\r\n", - `user-agent: Deno/${Deno.version.deno}\r\n`, - "accept-encoding: gzip, br\r\n", - `host: ${addr}\r\n\r\n`, - ].join(""); - assertEquals(actual, expected); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchUserSetTransferEncoding() { - const addr = `127.0.0.1:${listenPort}`; - const bufPromise = bufferServer(addr); - const response = await fetch(`http://${addr}/blah`, { - method: "POST", - headers: [ - ["Transfer-Encoding", "chunked"], - ], - }); - await response.arrayBuffer(); - assertEquals(response.status, 404); - assertEquals(response.headers.get("Content-Length"), "2"); - - const actual = new TextDecoder().decode((await bufPromise).bytes()); - const expected = [ - "POST /blah HTTP/1.1\r\n", - "content-length: 0\r\n", - `host: ${addr}\r\n`, - "accept: */*\r\n", - "accept-language: *\r\n", - `user-agent: Deno/${Deno.version.deno}\r\n`, - "accept-encoding: gzip, br\r\n\r\n", - ].join(""); - assertEquals(actual, expected); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchWithNonAsciiRedirection() { - const response = await fetch("http://localhost:4545/non_ascii_redirect", { - redirect: "manual", - }); - assertEquals(response.status, 301); - assertEquals(response.headers.get("location"), "/redirect®"); - await response.text(); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchWithManualRedirection() { - const response = await fetch("http://localhost:4546/", { - redirect: "manual", - }); // will redirect to http://localhost:4545/ - assertEquals(response.status, 301); - assertEquals(response.url, "http://localhost:4546/"); - assertEquals(response.type, "basic"); - assertEquals(response.headers.get("Location"), "http://localhost:4545/"); - await response.body!.cancel(); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchWithErrorRedirection() { - await assertRejects( - () => - fetch("http://localhost:4546/", { - redirect: "error", - }), - TypeError, - "redirect", - ); - }, -); - -Deno.test(function responseRedirect() { - const redir = Response.redirect("http://example.com/newLocation", 301); - assertEquals(redir.status, 301); - assertEquals(redir.statusText, ""); - assertEquals(redir.url, ""); - assertEquals( - redir.headers.get("Location"), - "http://example.com/newLocation", - ); - assertEquals(redir.type, "default"); -}); - -Deno.test(function responseRedirectTakeURLObjectAsParameter() { - const redir = Response.redirect(new URL("https://example.com/")); - assertEquals( - redir.headers.get("Location"), - "https://example.com/", - ); -}); - -Deno.test(async function responseWithoutBody() { - const response = new Response(); - assertEquals(await response.arrayBuffer(), new ArrayBuffer(0)); - const blob = await response.blob(); - assertEquals(blob.size, 0); - assertEquals(await blob.arrayBuffer(), new ArrayBuffer(0)); - assertEquals(await response.text(), ""); - await assertRejects(async () => { - await response.json(); - }); -}); - -Deno.test({ permissions: { net: true } }, async function fetchBodyReadTwice() { - const response = await fetch("http://localhost:4545/assets/fixture.json"); - - // Read body - const _json = await response.json(); - assert(_json); - - // All calls after the body was consumed, should fail - const methods = ["json", "text", "formData", "arrayBuffer"] as const; - for (const method of methods) { - try { - await response[method](); - fail( - "Reading body multiple times should failed, the stream should've been locked.", - ); - } catch { - // pass - } - } -}); - -Deno.test( - { permissions: { net: true } }, - async function fetchBodyReaderAfterRead() { - const response = await fetch( - "http://localhost:4545/assets/fixture.json", - ); - assert(response.body !== null); - const reader = await response.body.getReader(); - while (true) { - const { done, value } = await reader.read(); - if (done) break; - assert(value); - } - - try { - response.body.getReader(); - fail("The stream should've been locked."); - } catch { - // pass - } - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchBodyReaderWithCancelAndNewReader() { - const data = "a".repeat(1 << 10); - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: data, - }); - assert(response.body !== null); - const firstReader = await response.body.getReader(); - - // Acquire reader without reading & release - await firstReader.releaseLock(); - - const reader = await response.body.getReader(); - - let total = 0; - while (true) { - const { done, value } = await reader.read(); - if (done) break; - assert(value); - total += value.length; - } - - assertEquals(total, data.length); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchBodyReaderWithReadCancelAndNewReader() { - const data = "a".repeat(1 << 10); - - const response = await fetch("http://localhost:4545/echo_server", { - method: "POST", - body: data, - }); - assert(response.body !== null); - const firstReader = await response.body.getReader(); - - // Do one single read with first reader - const { value: firstValue } = await firstReader.read(); - assert(firstValue); - await firstReader.releaseLock(); - - // Continue read with second reader - const reader = await response.body.getReader(); - let total = firstValue.length || 0; - while (true) { - const { done, value } = await reader.read(); - if (done) break; - assert(value); - total += value.length; - } - assertEquals(total, data.length); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchResourceCloseAfterStreamCancel() { - const res = await fetch("http://localhost:4545/assets/fixture.json"); - assert(res.body !== null); - - // After ReadableStream.cancel is called, resource handle must be closed - // The test should not fail with: Test case is leaking resources - await res.body.cancel(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchNullBodyStatus() { - const nullBodyStatus = [101, 204, 205, 304]; - - for (const status of nullBodyStatus) { - const headers = new Headers([["x-status", String(status)]]); - const res = await fetch("http://localhost:4545/echo_server", { - body: "deno", - method: "POST", - headers, - }); - assertEquals(res.body, null); - assertEquals(res.status, status); - } - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchResponseContentLength() { - const body = new Uint8Array(2 ** 16); - const headers = new Headers([["content-type", "application/octet-stream"]]); - const res = await fetch("http://localhost:4545/echo_server", { - body: body, - method: "POST", - headers, - }); - assertEquals(Number(res.headers.get("content-length")), body.byteLength); - - const blob = await res.blob(); - // Make sure Body content-type is correctly set - assertEquals(blob.type, "application/octet-stream"); - assertEquals(blob.size, body.byteLength); - }, -); - -Deno.test(function fetchResponseConstructorNullBody() { - const nullBodyStatus = [204, 205, 304]; - - for (const status of nullBodyStatus) { - try { - new Response("deno", { status }); - fail("Response with null body status cannot have body"); - } catch (e) { - assert(e instanceof TypeError); - assertEquals( - e.message, - "Response with null body status cannot have body", - ); - } - } -}); - -Deno.test(function fetchResponseConstructorInvalidStatus() { - const invalidStatus = [100, 600, 199, null, "", NaN]; - - for (const status of invalidStatus) { - try { - // deno-lint-ignore ban-ts-comment - // @ts-ignore - new Response("deno", { status }); - fail(`Invalid status: ${status}`); - } catch (e) { - assert(e instanceof RangeError); - assert( - e.message.endsWith( - "is not equal to 101 and outside the range [200, 599].", - ), - ); - } - } -}); - -Deno.test(function fetchResponseEmptyConstructor() { - const response = new Response(); - assertEquals(response.status, 200); - assertEquals(response.body, null); - assertEquals(response.type, "default"); - assertEquals(response.url, ""); - assertEquals(response.redirected, false); - assertEquals(response.ok, true); - assertEquals(response.bodyUsed, false); - assertEquals([...response.headers], []); -}); - -Deno.test( - { permissions: { net: true, read: true } }, - async function fetchCustomHttpClientParamCertificateSuccess(): Promise< - void - > { - const caCert = Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem"); - const client = Deno.createHttpClient({ caCerts: [caCert] }); - const response = await fetch("https://localhost:5545/assets/fixture.json", { - client, - }); - const json = await response.json(); - assertEquals(json.name, "deno"); - client.close(); - }, -); - -Deno.test( - { permissions: { net: true, read: true } }, - function createHttpClientAcceptPoolIdleTimeout() { - const client = Deno.createHttpClient({ - poolIdleTimeout: 1000, - }); - client.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchCustomClientUserAgent(): Promise< - void - > { - const data = "Hello World"; - const client = Deno.createHttpClient({}); - const response = await fetch("http://localhost:4545/echo_server", { - client, - method: "POST", - body: new TextEncoder().encode(data), - }); - assertEquals( - response.headers.get("user-agent"), - `Deno/${Deno.version.deno}`, - ); - await response.text(); - client.close(); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function fetchPostBodyReadableStream() { - const addr = `127.0.0.1:${listenPort}`; - const bufPromise = bufferServer(addr); - const stream = new TransformStream(); - const writer = stream.writable.getWriter(); - // transformer writes don't resolve until they are read, so awaiting these - // will cause the transformer to hang, as the suspend the transformer, it - // is also illogical to await for the reads, as that is the whole point of - // streams is to have a "queue" which gets drained... - writer.write(new TextEncoder().encode("hello ")); - writer.write(new TextEncoder().encode("world")); - writer.close(); - const response = await fetch(`http://${addr}/blah`, { - method: "POST", - headers: [ - ["Hello", "World"], - ["Foo", "Bar"], - ], - body: stream.readable, - }); - await response.arrayBuffer(); - assertEquals(response.status, 404); - assertEquals(response.headers.get("Content-Length"), "2"); - - const actual = new TextDecoder().decode((await bufPromise).bytes()); - const expected = [ - "POST /blah HTTP/1.1\r\n", - "hello: World\r\n", - "foo: Bar\r\n", - "accept: */*\r\n", - "accept-language: *\r\n", - `user-agent: Deno/${Deno.version.deno}\r\n`, - "accept-encoding: gzip, br\r\n", - `host: ${addr}\r\n`, - `transfer-encoding: chunked\r\n\r\n`, - "B\r\n", - "hello world\r\n", - "0\r\n\r\n", - ].join(""); - assertEquals(actual, expected); - }, -); - -Deno.test({}, function fetchWritableRespProps() { - const original = new Response("https://deno.land", { - status: 404, - headers: { "x-deno": "foo" }, - }); - const new_ = new Response("https://deno.land", original); - assertEquals(original.status, new_.status); - assertEquals(new_.headers.get("x-deno"), "foo"); -}); - -Deno.test( - { permissions: { net: true } }, - async function fetchFilterOutCustomHostHeader(): Promise< - void - > { - const addr = `127.0.0.1:${listenPort}`; - const [hostname, port] = addr.split(":"); - const listener = Deno.listen({ - hostname, - port: Number(port), - }) as Deno.Listener; - - let httpConn: Deno.HttpConn; - listener.accept().then(async (conn: Deno.Conn) => { - httpConn = Deno.serveHttp(conn); - - await httpConn.nextRequest() - .then(async (requestEvent: Deno.RequestEvent | null) => { - const hostHeader = requestEvent?.request.headers.get("Host"); - const headersToReturn = hostHeader - ? { "Host": hostHeader } - : undefined; - - await requestEvent?.respondWith( - new Response("", { - status: 200, - headers: headersToReturn, - }), - ); - }); - }); - - const response = await fetch(`http://${addr}/`, { - headers: { "Host": "example.com" }, - }); - await response.text(); - listener.close(); - httpConn!.close(); - - assertEquals(response.headers.get("Host"), addr); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchNoServerReadableStreamBody() { - const completed = Promise.withResolvers<void>(); - const failed = Promise.withResolvers<void>(); - const body = new ReadableStream({ - start(controller) { - controller.enqueue(new Uint8Array([1])); - setTimeout(async () => { - // This is technically a race. If the fetch has failed by this point, the enqueue will - // throw. If not, it will succeed. Windows appears to take a while to time out the fetch, - // so we will just wait for that here before we attempt to enqueue so it's consistent - // across platforms. - await failed.promise; - assertThrows(() => controller.enqueue(new Uint8Array([2]))); - completed.resolve(); - }, 1000); - }, - }); - const nonExistentHostname = "http://localhost:47582"; - await assertRejects(async () => { - await fetch(nonExistentHostname, { body, method: "POST" }); - }, TypeError); - failed.resolve(); - await completed.promise; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchHeadRespBody() { - const res = await fetch("http://localhost:4545/echo_server", { - method: "HEAD", - }); - assertEquals(res.body, null); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function fetchClientCertWrongPrivateKey(): Promise<void> { - await assertRejects(async () => { - const client = Deno.createHttpClient({ - certChain: "bad data", - privateKey: await Deno.readTextFile( - "cli/tests/testdata/tls/localhost.key", - ), - }); - await fetch("https://localhost:5552/assets/fixture.json", { - client, - }); - }, Deno.errors.InvalidData); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function fetchClientCertBadPrivateKey(): Promise<void> { - await assertRejects(async () => { - const client = Deno.createHttpClient({ - certChain: await Deno.readTextFile( - "cli/tests/testdata/tls/localhost.crt", - ), - privateKey: "bad data", - }); - await fetch("https://localhost:5552/assets/fixture.json", { - client, - }); - }, Deno.errors.InvalidData); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function fetchClientCertNotPrivateKey(): Promise<void> { - await assertRejects(async () => { - const client = Deno.createHttpClient({ - certChain: await Deno.readTextFile( - "cli/tests/testdata/tls/localhost.crt", - ), - privateKey: "", - }); - await fetch("https://localhost:5552/assets/fixture.json", { - client, - }); - }, Deno.errors.InvalidData); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function fetchCustomClientPrivateKey(): Promise< - void - > { - const data = "Hello World"; - const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.crt"); - const client = Deno.createHttpClient({ - certChain: await Deno.readTextFile( - "cli/tests/testdata/tls/localhost.crt", - ), - privateKey: await Deno.readTextFile( - "cli/tests/testdata/tls/localhost.key", - ), - caCerts: [caCert], - }); - const response = await fetch("https://localhost:5552/echo_server", { - client, - method: "POST", - body: new TextEncoder().encode(data), - }); - assertEquals( - response.headers.get("user-agent"), - `Deno/${Deno.version.deno}`, - ); - await response.text(); - client.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchAbortWhileUploadStreaming(): Promise<void> { - const abortController = new AbortController(); - try { - await fetch( - "http://localhost:5552/echo_server", - { - method: "POST", - body: new ReadableStream({ - pull(controller) { - abortController.abort(); - controller.enqueue(new Uint8Array([1, 2, 3, 4])); - }, - }), - signal: abortController.signal, - }, - ); - fail("Fetch didn't reject."); - } catch (error) { - assert(error instanceof DOMException); - assertEquals(error.name, "AbortError"); - assertEquals(error.message, "The signal has been aborted"); - } - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchAbortWhileUploadStreamingWithReason(): Promise<void> { - const abortController = new AbortController(); - const abortReason = new Error(); - try { - await fetch( - "http://localhost:5552/echo_server", - { - method: "POST", - body: new ReadableStream({ - pull(controller) { - abortController.abort(abortReason); - controller.enqueue(new Uint8Array([1, 2, 3, 4])); - }, - }), - signal: abortController.signal, - }, - ); - fail("Fetch didn't reject."); - } catch (error) { - assertEquals(error, abortReason); - } - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchAbortWhileUploadStreamingWithPrimitiveReason(): Promise< - void - > { - const abortController = new AbortController(); - try { - await fetch( - "http://localhost:5552/echo_server", - { - method: "POST", - body: new ReadableStream({ - pull(controller) { - abortController.abort("Abort reason"); - controller.enqueue(new Uint8Array([1, 2, 3, 4])); - }, - }), - signal: abortController.signal, - }, - ); - fail("Fetch didn't reject."); - } catch (error) { - assertEquals(error, "Abort reason"); - } - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchHeaderValueShouldNotPanic() { - for (let i = 0; i < 0x21; i++) { - if (i === 0x09 || i === 0x0A || i === 0x0D || i === 0x20) { - continue; // these header value will be normalized, will not cause an error. - } - // ensure there will be an error instead of panic. - await assertRejects(() => - fetch("http://localhost:4545/echo_server", { - method: "HEAD", - headers: { "val": String.fromCharCode(i) }, - }), TypeError); - } - await assertRejects(() => - fetch("http://localhost:4545/echo_server", { - method: "HEAD", - headers: { "val": String.fromCharCode(127) }, - }), TypeError); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchHeaderNameShouldNotPanic() { - const validTokens = - "!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUWVXYZ^_`abcdefghijklmnopqrstuvwxyz|~" - .split(""); - for (let i = 0; i <= 255; i++) { - const token = String.fromCharCode(i); - if (validTokens.includes(token)) { - continue; - } - // ensure there will be an error instead of panic. - await assertRejects(() => - fetch("http://localhost:4545/echo_server", { - method: "HEAD", - headers: { [token]: "value" }, - }), TypeError); - } - await assertRejects(() => - fetch("http://localhost:4545/echo_server", { - method: "HEAD", - headers: { "": "value" }, - }), TypeError); - }, -); - -Deno.test( - { permissions: { net: true, read: true } }, - async function fetchSupportsHttpsOverIpAddress() { - const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem"); - const client = Deno.createHttpClient({ caCerts: [caCert] }); - const res = await fetch("https://localhost:5546/http_version", { client }); - assert(res.ok); - assertEquals(await res.text(), "HTTP/1.1"); - client.close(); - }, -); - -Deno.test( - { permissions: { net: true, read: true } }, - async function fetchSupportsHttp1Only() { - const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem"); - const client = Deno.createHttpClient({ caCerts: [caCert] }); - const res = await fetch("https://localhost:5546/http_version", { client }); - assert(res.ok); - assertEquals(await res.text(), "HTTP/1.1"); - client.close(); - }, -); - -Deno.test( - { permissions: { net: true, read: true } }, - async function fetchSupportsHttp2() { - const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem"); - const client = Deno.createHttpClient({ caCerts: [caCert] }); - const res = await fetch("https://localhost:5547/http_version", { client }); - assert(res.ok); - assertEquals(await res.text(), "HTTP/2.0"); - client.close(); - }, -); - -Deno.test( - { permissions: { net: true, read: true } }, - async function fetchForceHttp1OnHttp2Server() { - const client = Deno.createHttpClient({ http2: false, http1: true }); - await assertRejects( - () => fetch("http://localhost:5549/http_version", { client }), - TypeError, - ); - client.close(); - }, -); - -Deno.test( - { permissions: { net: true, read: true } }, - async function fetchForceHttp2OnHttp1Server() { - const client = Deno.createHttpClient({ http2: true, http1: false }); - await assertRejects( - () => fetch("http://localhost:5548/http_version", { client }), - TypeError, - ); - client.close(); - }, -); - -Deno.test( - { permissions: { net: true, read: true } }, - async function fetchPrefersHttp2() { - const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem"); - const client = Deno.createHttpClient({ caCerts: [caCert] }); - const res = await fetch("https://localhost:5545/http_version", { client }); - assert(res.ok); - assertEquals(await res.text(), "HTTP/2.0"); - client.close(); - }, -); - -Deno.test( - { permissions: { net: true, read: true } }, - async function createHttpClientAllowHost() { - const client = Deno.createHttpClient({ - allowHost: true, - }); - const res = await fetch("http://localhost:4545/echo_server", { - headers: { - "host": "example.com", - }, - client, - }); - assert(res.ok); - assertEquals(res.headers.get("host"), "example.com"); - await res.body?.cancel(); - client.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function createHttpClientExplicitResourceManagement() { - using client = Deno.createHttpClient({}); - const response = await fetch("http://localhost:4545/assets/fixture.json", { - client, - }); - const json = await response.json(); - assertEquals(json.name, "deno"); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function createHttpClientExplicitResourceManagementDoubleClose() { - using client = Deno.createHttpClient({}); - const response = await fetch("http://localhost:4545/assets/fixture.json", { - client, - }); - const json = await response.json(); - assertEquals(json.name, "deno"); - // Close the client even though we declared it with `using` to confirm that - // the cleanup done as per `Symbol.dispose` will not throw any errors. - client.close(); - }, -); - -Deno.test({ permissions: { read: false } }, async function fetchFilePerm() { - await assertRejects(async () => { - await fetch(import.meta.resolve("../testdata/subdir/json_1.json")); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { permissions: { read: false } }, - async function fetchFilePermDoesNotExist() { - await assertRejects(async () => { - await fetch(import.meta.resolve("./bad.json")); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true } }, - async function fetchFileBadMethod() { - await assertRejects( - async () => { - await fetch( - import.meta.resolve("../testdata/subdir/json_1.json"), - { - method: "POST", - }, - ); - }, - TypeError, - "Fetching files only supports the GET method. Received POST.", - ); - }, -); - -Deno.test( - { permissions: { read: true } }, - async function fetchFileDoesNotExist() { - await assertRejects( - async () => { - await fetch(import.meta.resolve("./bad.json")); - }, - TypeError, - ); - }, -); - -Deno.test( - { permissions: { read: true } }, - async function fetchFile() { - const res = await fetch( - import.meta.resolve("../testdata/subdir/json_1.json"), - ); - assert(res.ok); - const fixture = await Deno.readTextFile( - "cli/tests/testdata/subdir/json_1.json", - ); - assertEquals(await res.text(), fixture); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchContentLengthPost() { - const response = await fetch("http://localhost:4545/content_length", { - method: "POST", - }); - const length = await response.text(); - assertEquals(length, 'Some("0")'); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchContentLengthPut() { - const response = await fetch("http://localhost:4545/content_length", { - method: "PUT", - }); - const length = await response.text(); - assertEquals(length, 'Some("0")'); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchContentLengthPatch() { - const response = await fetch("http://localhost:4545/content_length", { - method: "PATCH", - }); - const length = await response.text(); - assertEquals(length, "None"); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchContentLengthPostWithStringBody() { - const response = await fetch("http://localhost:4545/content_length", { - method: "POST", - body: "Hey!", - }); - const length = await response.text(); - assertEquals(length, 'Some("4")'); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchContentLengthPostWithBufferBody() { - const response = await fetch("http://localhost:4545/content_length", { - method: "POST", - body: new TextEncoder().encode("Hey!"), - }); - const length = await response.text(); - assertEquals(length, 'Some("4")'); - }, -); - -Deno.test(async function staticResponseJson() { - const data = { hello: "world" }; - const resp = Response.json(data); - assertEquals(resp.status, 200); - assertEquals(resp.headers.get("content-type"), "application/json"); - const res = await resp.json(); - assertEquals(res, data); -}); - -function invalidServer(addr: string, body: Uint8Array): Deno.Listener { - const [hostname, port] = addr.split(":"); - const listener = Deno.listen({ - hostname, - port: Number(port), - }) as Deno.Listener; - - (async () => { - for await (const conn of listener) { - const p1 = conn.read(new Uint8Array(2 ** 14)); - const p2 = conn.write(body); - - await Promise.all([p1, p2]); - conn.close(); - } - })(); - - return listener; -} - -Deno.test( - { permissions: { net: true } }, - async function fetchWithInvalidContentLengthAndTransferEncoding(): Promise< - void - > { - const addr = `127.0.0.1:${listenPort}`; - const data = "a".repeat(10 << 10); - - const body = new TextEncoder().encode( - `HTTP/1.1 200 OK\r\nContent-Length: ${ - Math.round(data.length * 2) - }\r\nTransfer-Encoding: chunked\r\n\r\n${ - data.length.toString(16) - }\r\n${data}\r\n0\r\n\r\n`, - ); - - // if transfer-encoding is sent, content-length is ignored - // even if it has an invalid value (content-length > totalLength) - const listener = invalidServer(addr, body); - const response = await fetch(`http://${addr}/`); - - const res = await response.arrayBuffer(); - const buf = new TextEncoder().encode(data); - assertEquals(res.byteLength, buf.byteLength); - assertEquals(new Uint8Array(res), buf); - - listener.close(); - }, -); - -Deno.test( - // TODO(bartlomieju): reenable this test - // https://github.com/denoland/deno/issues/18350 - { ignore: Deno.build.os === "windows", permissions: { net: true } }, - async function fetchWithInvalidContentLength(): Promise< - void - > { - const addr = `127.0.0.1:${listenPort}`; - const data = "a".repeat(10 << 10); - - const body = new TextEncoder().encode( - `HTTP/1.1 200 OK\r\nContent-Length: ${ - Math.round(data.length / 2) - }\r\nContent-Length: ${data.length}\r\n\r\n${data}`, - ); - - // It should fail if multiple content-length headers with different values are sent - const listener = invalidServer(addr, body); - await assertRejects( - async () => { - await fetch(`http://${addr}/`); - }, - TypeError, - "invalid content-length parsed", - ); - - listener.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchWithInvalidContentLength2(): Promise< - void - > { - const addr = `127.0.0.1:${listenPort}`; - const data = "a".repeat(10 << 10); - - const contentLength = data.length / 2; - const body = new TextEncoder().encode( - `HTTP/1.1 200 OK\r\nContent-Length: ${contentLength}\r\n\r\n${data}`, - ); - - const listener = invalidServer(addr, body); - const response = await fetch(`http://${addr}/`); - - // If content-length < totalLength, a maximum of content-length bytes - // should be returned. - const res = await response.arrayBuffer(); - const buf = new TextEncoder().encode(data); - assertEquals(res.byteLength, contentLength); - assertEquals(new Uint8Array(res), buf.subarray(contentLength)); - - listener.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchWithInvalidContentLength3(): Promise< - void - > { - const addr = `127.0.0.1:${listenPort}`; - const data = "a".repeat(10 << 10); - - const contentLength = data.length * 2; - const body = new TextEncoder().encode( - `HTTP/1.1 200 OK\r\nContent-Length: ${contentLength}\r\n\r\n${data}`, - ); - - const listener = invalidServer(addr, body); - const response = await fetch(`http://${addr}/`); - // If content-length > totalLength, a maximum of content-length bytes - // should be returned. - await assertRejects( - async () => { - await response.arrayBuffer(); - }, - Error, - "end of file before message length reached", - ); - - listener.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchBlobUrl(): Promise<void> { - const blob = new Blob(["ok"], { type: "text/plain" }); - const url = URL.createObjectURL(blob); - assert(url.startsWith("blob:"), `URL was ${url}`); - const res = await fetch(url); - assertEquals(res.url, url); - assertEquals(res.status, 200); - assertEquals(res.headers.get("content-length"), "2"); - assertEquals(res.headers.get("content-type"), "text/plain"); - assertEquals(await res.text(), "ok"); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchResponseStreamIsLockedWhileReading() { - const response = await fetch("http://localhost:4545/echo_server", { - body: new Uint8Array(5000), - method: "POST", - }); - - assertEquals(response.body!.locked, false); - const promise = response.arrayBuffer(); - assertEquals(response.body!.locked, true); - - await promise; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchConstructorClones() { - const req = new Request("https://example.com", { - method: "POST", - body: "foo", - }); - assertEquals(await req.text(), "foo"); - await assertRejects(() => req.text()); - - const req2 = new Request(req, { method: "PUT", body: "bar" }); // should not have any impact on req - await assertRejects(() => req.text()); - assertEquals(await req2.text(), "bar"); - - assertEquals(req.method, "POST"); - assertEquals(req2.method, "PUT"); - - assertEquals(req.headers.get("x-foo"), null); - assertEquals(req2.headers.get("x-foo"), null); - req2.headers.set("x-foo", "bar"); // should not have any impact on req - assertEquals(req.headers.get("x-foo"), null); - assertEquals(req2.headers.get("x-foo"), "bar"); - }, -); - -Deno.test( - // TODO(bartlomieju): reenable this test - // https://github.com/denoland/deno/issues/18350 - { ignore: Deno.build.os === "windows", permissions: { net: true } }, - async function fetchRequestBodyErrorCatchable() { - const listener = Deno.listen({ hostname: "127.0.0.1", port: listenPort }); - const server = (async () => { - const conn = await listener.accept(); - listener.close(); - const buf = new Uint8Array(256); - const n = await conn.read(buf); - const data = new TextDecoder().decode(buf.subarray(0, n!)); // this is the request headers + first body chunk - assert(data.startsWith("POST / HTTP/1.1\r\n")); - assert(data.endsWith("1\r\na\r\n")); - const n2 = await conn.read(buf); - assertEquals(n2, 6); // this is the second body chunk - const n3 = await conn.read(buf); - assertEquals(n3, null); // the connection now abruptly closes because the client has errored - conn.close(); - })(); - - const stream = new ReadableStream({ - async start(controller) { - controller.enqueue(new TextEncoder().encode("a")); - await delay(1000); - controller.enqueue(new TextEncoder().encode("b")); - await delay(1000); - controller.error(new Error("foo")); - }, - }); - - const err = await assertRejects(() => - fetch(`http://localhost:${listenPort}/`, { - body: stream, - method: "POST", - }) - ); - - assert(err instanceof TypeError, `err was not a TypeError ${err}`); - assert(err.cause, `err.cause was null ${err}`); - assert( - err.cause instanceof Error, - `err.cause was not an Error ${err.cause}`, - ); - assertEquals(err.cause.message, "foo"); - - await server; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function fetchRequestBodyEmptyStream() { - const body = new ReadableStream({ - start(controller) { - controller.enqueue(new Uint8Array([])); - controller.close(); - }, - }); - - await assertRejects( - async () => { - const controller = new AbortController(); - const promise = fetch("http://localhost:4545/echo_server", { - body, - method: "POST", - signal: controller.signal, - }); - try { - controller.abort(); - } catch (e) { - console.log(e); - fail("abort should not throw"); - } - await promise; - }, - DOMException, - "The signal has been aborted", - ); - }, -); - -Deno.test("Request with subarray TypedArray body", async () => { - const body = new Uint8Array([1, 2, 3, 4, 5]).subarray(1); - const req = new Request("https://example.com", { method: "POST", body }); - const actual = new Uint8Array(await req.arrayBuffer()); - const expected = new Uint8Array([2, 3, 4, 5]); - assertEquals(actual, expected); -}); - -Deno.test("Response with subarray TypedArray body", async () => { - const body = new Uint8Array([1, 2, 3, 4, 5]).subarray(1); - const req = new Response(body); - const actual = new Uint8Array(await req.arrayBuffer()); - const expected = new Uint8Array([2, 3, 4, 5]); - assertEquals(actual, expected); -}); diff --git a/cli/tests/unit/ffi_test.ts b/cli/tests/unit/ffi_test.ts deleted file mode 100644 index 2b56a8db1..000000000 --- a/cli/tests/unit/ffi_test.ts +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assertEquals, assertRejects, assertThrows } from "./test_util.ts"; - -Deno.test({ permissions: { ffi: true } }, function dlopenInvalidArguments() { - const filename = "/usr/lib/libc.so.6"; - assertThrows(() => { - // @ts-expect-error: ForeignFunction cannot be null - Deno.dlopen(filename, { malloc: null }); - }, TypeError); - assertThrows(() => { - Deno.dlopen(filename, { - // @ts-expect-error: invalid NativeType - malloc: { parameters: ["a"], result: "b" }, - }); - }, TypeError); - assertThrows(() => { - // @ts-expect-error: DynamicLibrary symbols cannot be null - Deno.dlopen(filename, null); - }, TypeError); - assertThrows(() => { - // @ts-expect-error: require 2 arguments - Deno.dlopen(filename); - }, TypeError); -}); - -Deno.test({ permissions: { ffi: false } }, function ffiPermissionDenied() { - assertThrows(() => { - Deno.dlopen("/usr/lib/libc.so.6", {}); - }, Deno.errors.PermissionDenied); - const fnptr = new Deno.UnsafeFnPointer( - // @ts-expect-error: Not NonNullable but null check is after permissions check. - null, - { - parameters: ["u32", "pointer"], - result: "void", - } as const, - ); - assertThrows(() => { - fnptr.call(123, null); - }, Deno.errors.PermissionDenied); - assertThrows(() => { - Deno.UnsafePointer.of(new Uint8Array(0)); - }, Deno.errors.PermissionDenied); - const ptrView = new Deno.UnsafePointerView( - // @ts-expect-error: Not NonNullable but null check is after permissions check. - null, - ); - assertThrows(() => { - ptrView.copyInto(new Uint8Array(0)); - }, Deno.errors.PermissionDenied); - assertThrows(() => { - ptrView.getCString(); - }, Deno.errors.PermissionDenied); - assertThrows(() => { - ptrView.getUint8(); - }, Deno.errors.PermissionDenied); - assertThrows(() => { - ptrView.getInt8(); - }, Deno.errors.PermissionDenied); - assertThrows(() => { - ptrView.getUint16(); - }, Deno.errors.PermissionDenied); - assertThrows(() => { - ptrView.getInt16(); - }, Deno.errors.PermissionDenied); - assertThrows(() => { - ptrView.getUint32(); - }, Deno.errors.PermissionDenied); - assertThrows(() => { - ptrView.getInt32(); - }, Deno.errors.PermissionDenied); - assertThrows(() => { - ptrView.getFloat32(); - }, Deno.errors.PermissionDenied); - assertThrows(() => { - ptrView.getFloat64(); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { ffi: true } }, function pointerOf() { - const buffer = new ArrayBuffer(1024); - const baseAddress = Deno.UnsafePointer.value(Deno.UnsafePointer.of(buffer)); - const uint8Address = Deno.UnsafePointer.value( - Deno.UnsafePointer.of(new Uint8Array(buffer)), - ); - assertEquals(baseAddress, uint8Address); - const float64Address = Deno.UnsafePointer.value( - Deno.UnsafePointer.of(new Float64Array(buffer)), - ); - assertEquals(baseAddress, float64Address); - const uint8AddressOffset = Deno.UnsafePointer.value( - Deno.UnsafePointer.of(new Uint8Array(buffer, 100)), - ); - assertEquals(Number(baseAddress) + 100, uint8AddressOffset); - const float64AddressOffset = Deno.UnsafePointer.value( - Deno.UnsafePointer.of(new Float64Array(buffer, 80)), - ); - assertEquals(Number(baseAddress) + 80, float64AddressOffset); -}); - -Deno.test({ permissions: { ffi: true } }, function callWithError() { - const throwCb = () => { - throw new Error("Error"); - }; - const cb = new Deno.UnsafeCallback({ - parameters: [], - result: "void", - }, throwCb); - const fnPointer = new Deno.UnsafeFnPointer(cb.pointer, { - parameters: [], - result: "void", - }); - assertThrows(() => fnPointer.call()); - cb.close(); -}); - -Deno.test( - { permissions: { ffi: true }, ignore: true }, - async function callNonBlockingWithError() { - const throwCb = () => { - throw new Error("Error"); - }; - const cb = new Deno.UnsafeCallback({ - parameters: [], - result: "void", - }, throwCb); - const fnPointer = new Deno.UnsafeFnPointer(cb.pointer, { - parameters: [], - result: "void", - nonblocking: true, - }); - // TODO(mmastrac): currently ignored as we do not thread callback exceptions through nonblocking pointers - await assertRejects(async () => await fnPointer.call()); - cb.close(); - }, -); diff --git a/cli/tests/unit/file_test.ts b/cli/tests/unit/file_test.ts deleted file mode 100644 index 1af3a3f84..000000000 --- a/cli/tests/unit/file_test.ts +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals } from "./test_util.ts"; - -// deno-lint-ignore no-explicit-any -function testFirstArgument(arg1: any[], expectedSize: number) { - const file = new File(arg1, "name"); - assert(file instanceof File); - assertEquals(file.name, "name"); - assertEquals(file.size, expectedSize); - assertEquals(file.type, ""); -} - -Deno.test(function fileEmptyFileBits() { - testFirstArgument([], 0); -}); - -Deno.test(function fileStringFileBits() { - testFirstArgument(["bits"], 4); -}); - -Deno.test(function fileUnicodeStringFileBits() { - testFirstArgument(["𝓽𝓮𝔁𝓽"], 16); -}); - -Deno.test(function fileStringObjectFileBits() { - testFirstArgument([new String("string object")], 13); -}); - -Deno.test(function fileEmptyBlobFileBits() { - testFirstArgument([new Blob()], 0); -}); - -Deno.test(function fileBlobFileBits() { - testFirstArgument([new Blob(["bits"])], 4); -}); - -Deno.test(function fileEmptyFileFileBits() { - testFirstArgument([new File([], "world.txt")], 0); -}); - -Deno.test(function fileFileFileBits() { - testFirstArgument([new File(["bits"], "world.txt")], 4); -}); - -Deno.test(function fileArrayBufferFileBits() { - testFirstArgument([new ArrayBuffer(8)], 8); -}); - -Deno.test(function fileTypedArrayFileBits() { - testFirstArgument([new Uint8Array([0x50, 0x41, 0x53, 0x53])], 4); -}); - -Deno.test(function fileVariousFileBits() { - testFirstArgument( - [ - "bits", - new Blob(["bits"]), - new Blob(), - new Uint8Array([0x50, 0x41]), - new Uint16Array([0x5353]), - new Uint32Array([0x53534150]), - ], - 16, - ); -}); - -Deno.test(function fileNumberInFileBits() { - testFirstArgument([12], 2); -}); - -Deno.test(function fileArrayInFileBits() { - testFirstArgument([[1, 2, 3]], 5); -}); - -Deno.test(function fileObjectInFileBits() { - // "[object Object]" - testFirstArgument([{}], 15); -}); - -// deno-lint-ignore no-explicit-any -function testSecondArgument(arg2: any, expectedFileName: string) { - const file = new File(["bits"], arg2); - assert(file instanceof File); - assertEquals(file.name, expectedFileName); -} - -Deno.test(function fileUsingFileName() { - testSecondArgument("dummy", "dummy"); -}); - -Deno.test(function fileUsingNullFileName() { - testSecondArgument(null, "null"); -}); - -Deno.test(function fileUsingNumberFileName() { - testSecondArgument(1, "1"); -}); - -Deno.test(function fileUsingEmptyStringFileName() { - testSecondArgument("", ""); -}); - -Deno.test(function inspectFile() { - assertEquals( - Deno.inspect(new File([], "file-name.txt")), - `File { name: "file-name.txt", size: 0, type: "" }`, - ); - assertEquals( - Deno.inspect(new File([], "file-name.txt", { type: "text/plain" })), - `File { name: "file-name.txt", size: 0, type: "text/plain" }`, - ); -}); diff --git a/cli/tests/unit/filereader_test.ts b/cli/tests/unit/filereader_test.ts deleted file mode 100644 index 158cf5383..000000000 --- a/cli/tests/unit/filereader_test.ts +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "./test_util.ts"; - -Deno.test(function fileReaderConstruct() { - const fr = new FileReader(); - assertEquals(fr.readyState, FileReader.EMPTY); - - assertEquals(FileReader.EMPTY, 0); - assertEquals(FileReader.LOADING, 1); - assertEquals(FileReader.DONE, 2); -}); - -Deno.test(async function fileReaderLoadBlob() { - await new Promise<void>((resolve) => { - const fr = new FileReader(); - const b1 = new Blob(["Hello World"]); - - assertEquals(fr.readyState, FileReader.EMPTY); - - const hasOnEvents = { - load: false, - loadend: false, - loadstart: false, - progress: 0, - }; - const hasDispatchedEvents = { - load: false, - loadend: false, - loadstart: false, - progress: 0, - }; - let result: string | null = null; - - fr.addEventListener("load", () => { - hasDispatchedEvents.load = true; - }); - fr.addEventListener("loadend", () => { - hasDispatchedEvents.loadend = true; - }); - fr.addEventListener("loadstart", () => { - hasDispatchedEvents.loadstart = true; - }); - fr.addEventListener("progress", () => { - hasDispatchedEvents.progress += 1; - }); - - fr.onloadstart = () => { - hasOnEvents.loadstart = true; - }; - fr.onprogress = () => { - assertEquals(fr.readyState, FileReader.LOADING); - - hasOnEvents.progress += 1; - }; - fr.onload = () => { - hasOnEvents.load = true; - }; - fr.onloadend = (ev) => { - hasOnEvents.loadend = true; - result = fr.result as string; - - assertEquals(hasOnEvents.loadstart, true); - assertEquals(hasDispatchedEvents.loadstart, true); - assertEquals(hasOnEvents.load, true); - assertEquals(hasDispatchedEvents.load, true); - assertEquals(hasOnEvents.loadend, true); - assertEquals(hasDispatchedEvents.loadend, true); - - assertEquals(fr.readyState, FileReader.DONE); - - assertEquals(result, "Hello World"); - assertEquals(ev.lengthComputable, true); - resolve(); - }; - - fr.readAsText(b1); - }); -}); - -Deno.test(async function fileReaderLoadBlobDouble() { - // impl note from https://w3c.github.io/FileAPI/ - // Event handler for the load or error events could have started another load, - // if that happens the loadend event for the first load is not fired - - const fr = new FileReader(); - const b1 = new Blob(["First load"]); - const b2 = new Blob(["Second load"]); - - await new Promise<void>((resolve) => { - let result: string | null = null; - - fr.onload = () => { - result = fr.result as string; - assertEquals(result === "First load" || result === "Second load", true); - - if (result === "First load") { - fr.readAsText(b2); - } - }; - fr.onloadend = () => { - assertEquals(result, "Second load"); - - resolve(); - }; - - fr.readAsText(b1); - }); -}); - -Deno.test(async function fileReaderLoadBlobArrayBuffer() { - await new Promise<void>((resolve) => { - const fr = new FileReader(); - const b1 = new Blob(["Hello World"]); - let result: ArrayBuffer | null = null; - - fr.onloadend = (ev) => { - assertEquals(fr.result instanceof ArrayBuffer, true); - result = fr.result as ArrayBuffer; - - const decoder = new TextDecoder(); - const text = decoder.decode(result); - - assertEquals(text, "Hello World"); - assertEquals(ev.lengthComputable, true); - resolve(); - }; - - fr.readAsArrayBuffer(b1); - }); -}); - -Deno.test(async function fileReaderLoadBlobDataUrl() { - await new Promise<void>((resolve) => { - const fr = new FileReader(); - const b1 = new Blob(["Hello World"]); - let result: string | null = null; - - fr.onloadend = (ev) => { - result = fr.result as string; - assertEquals( - result, - "data:application/octet-stream;base64,SGVsbG8gV29ybGQ=", - ); - assertEquals(ev.lengthComputable, true); - resolve(); - }; - - fr.readAsDataURL(b1); - }); -}); - -Deno.test(async function fileReaderLoadBlobAbort() { - await new Promise<void>((resolve) => { - const fr = new FileReader(); - const b1 = new Blob(["Hello World"]); - - const hasOnEvents = { - load: false, - loadend: false, - abort: false, - }; - - fr.onload = () => { - hasOnEvents.load = true; - }; - fr.onloadend = (ev) => { - hasOnEvents.loadend = true; - - assertEquals(hasOnEvents.load, false); - assertEquals(hasOnEvents.loadend, true); - assertEquals(hasOnEvents.abort, true); - - assertEquals(fr.readyState, FileReader.DONE); - assertEquals(fr.result, null); - assertEquals(ev.lengthComputable, false); - resolve(); - }; - fr.onabort = () => { - hasOnEvents.abort = true; - }; - - fr.readAsDataURL(b1); - fr.abort(); - }); -}); - -Deno.test(async function fileReaderLoadBlobAbort() { - await new Promise<void>((resolve) => { - const fr = new FileReader(); - const b1 = new Blob(["Hello World"]); - - const hasOnEvents = { - load: false, - loadend: false, - abort: false, - }; - - fr.onload = () => { - hasOnEvents.load = true; - }; - fr.onloadend = (ev) => { - hasOnEvents.loadend = true; - - assertEquals(hasOnEvents.load, false); - assertEquals(hasOnEvents.loadend, true); - assertEquals(hasOnEvents.abort, true); - - assertEquals(fr.readyState, FileReader.DONE); - assertEquals(fr.result, null); - assertEquals(ev.lengthComputable, false); - resolve(); - }; - fr.onabort = () => { - hasOnEvents.abort = true; - }; - - fr.readAsDataURL(b1); - fr.abort(); - }); -}); - -Deno.test( - async function fileReaderDispatchesEventsInCorrectOrder() { - await new Promise<void>((resolve) => { - const fr = new FileReader(); - const b1 = new Blob(["Hello World"]); - let out = ""; - fr.addEventListener("loadend", () => { - out += "1"; - }); - fr.onloadend = (_ev) => { - out += "2"; - }; - fr.addEventListener("loadend", () => { - assertEquals(out, "12"); - resolve(); - }); - - fr.readAsDataURL(b1); - }); - }, -); diff --git a/cli/tests/unit/files_test.ts b/cli/tests/unit/files_test.ts deleted file mode 100644 index 2501ea643..000000000 --- a/cli/tests/unit/files_test.ts +++ /dev/null @@ -1,1095 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -// deno-lint-ignore-file no-deprecated-deno-api - -import { - assert, - assertEquals, - assertRejects, - assertThrows, -} from "./test_util.ts"; -import { copy } from "@test_util/std/streams/copy.ts"; - -Deno.test(function filesStdioFileDescriptors() { - assertEquals(Deno.stdin.rid, 0); - assertEquals(Deno.stdout.rid, 1); - assertEquals(Deno.stderr.rid, 2); -}); - -Deno.test({ permissions: { read: true } }, async function filesCopyToStdout() { - const filename = "cli/tests/testdata/assets/fixture.json"; - using file = await Deno.open(filename); - assert(file instanceof Deno.File); - assert(file instanceof Deno.FsFile); - assert(file.rid > 2); - const bytesWritten = await copy(file, Deno.stdout); - const fileSize = Deno.statSync(filename).size; - assertEquals(bytesWritten, fileSize); -}); - -Deno.test({ permissions: { read: true } }, async function filesIter() { - const filename = "cli/tests/testdata/assets/hello.txt"; - using file = await Deno.open(filename); - - let totalSize = 0; - for await (const buf of Deno.iter(file)) { - totalSize += buf.byteLength; - } - - assertEquals(totalSize, 12); -}); - -Deno.test( - { permissions: { read: true } }, - async function filesIterCustomBufSize() { - const filename = "cli/tests/testdata/assets/hello.txt"; - using file = await Deno.open(filename); - - let totalSize = 0; - let iterations = 0; - for await (const buf of Deno.iter(file, { bufSize: 6 })) { - totalSize += buf.byteLength; - iterations += 1; - } - - assertEquals(totalSize, 12); - assertEquals(iterations, 2); - }, -); - -Deno.test({ permissions: { read: true } }, function filesIterSync() { - const filename = "cli/tests/testdata/assets/hello.txt"; - using file = Deno.openSync(filename); - - let totalSize = 0; - for (const buf of Deno.iterSync(file)) { - totalSize += buf.byteLength; - } - - assertEquals(totalSize, 12); -}); - -Deno.test( - { permissions: { read: true } }, - function filesIterSyncCustomBufSize() { - const filename = "cli/tests/testdata/assets/hello.txt"; - using file = Deno.openSync(filename); - - let totalSize = 0; - let iterations = 0; - for (const buf of Deno.iterSync(file, { bufSize: 6 })) { - totalSize += buf.byteLength; - iterations += 1; - } - - assertEquals(totalSize, 12); - assertEquals(iterations, 2); - }, -); - -Deno.test(async function readerIter() { - // ref: https://github.com/denoland/deno/issues/2330 - const encoder = new TextEncoder(); - - class TestReader implements Deno.Reader { - #offset = 0; - #buf: Uint8Array; - - constructor(s: string) { - this.#buf = new Uint8Array(encoder.encode(s)); - } - - read(p: Uint8Array): Promise<number | null> { - const n = Math.min(p.byteLength, this.#buf.byteLength - this.#offset); - p.set(this.#buf.slice(this.#offset, this.#offset + n)); - this.#offset += n; - - if (n === 0) { - return Promise.resolve(null); - } - - return Promise.resolve(n); - } - } - - const reader = new TestReader("hello world!"); - - let totalSize = 0; - for await (const buf of Deno.iter(reader)) { - totalSize += buf.byteLength; - } - - assertEquals(totalSize, 12); -}); - -Deno.test(async function readerIterSync() { - // ref: https://github.com/denoland/deno/issues/2330 - const encoder = new TextEncoder(); - - class TestReader implements Deno.ReaderSync { - #offset = 0; - #buf: Uint8Array; - - constructor(s: string) { - this.#buf = new Uint8Array(encoder.encode(s)); - } - - readSync(p: Uint8Array): number | null { - const n = Math.min(p.byteLength, this.#buf.byteLength - this.#offset); - p.set(this.#buf.slice(this.#offset, this.#offset + n)); - this.#offset += n; - - if (n === 0) { - return null; - } - - return n; - } - } - - const reader = new TestReader("hello world!"); - - let totalSize = 0; - for await (const buf of Deno.iterSync(reader)) { - totalSize += buf.byteLength; - } - - assertEquals(totalSize, 12); -}); - -Deno.test( - { - permissions: { read: true, write: true }, - }, - function openSyncMode() { - const path = Deno.makeTempDirSync() + "/test_openSync.txt"; - using _file = Deno.openSync(path, { - write: true, - createNew: true, - mode: 0o626, - }); - const pathInfo = Deno.statSync(path); - if (Deno.build.os !== "windows") { - assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask()); - } - }, -); - -Deno.test( - { - permissions: { read: true, write: true }, - }, - async function openMode() { - const path = (await Deno.makeTempDir()) + "/test_open.txt"; - using _file = await Deno.open(path, { - write: true, - createNew: true, - mode: 0o626, - }); - const pathInfo = Deno.statSync(path); - if (Deno.build.os !== "windows") { - assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask()); - } - }, -); - -Deno.test( - { - permissions: { read: true, write: true }, - }, - function openSyncUrl() { - const tempDir = Deno.makeTempDirSync(); - const fileUrl = new URL( - `file://${ - Deno.build.os === "windows" ? "/" : "" - }${tempDir}/test_open.txt`, - ); - using _file = Deno.openSync(fileUrl, { - write: true, - createNew: true, - mode: 0o626, - }); - const pathInfo = Deno.statSync(fileUrl); - if (Deno.build.os !== "windows") { - assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask()); - } - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -Deno.test( - { - permissions: { read: true, write: true }, - }, - async function openUrl() { - const tempDir = await Deno.makeTempDir(); - const fileUrl = new URL( - `file://${ - Deno.build.os === "windows" ? "/" : "" - }${tempDir}/test_open.txt`, - ); - using _file = await Deno.open(fileUrl, { - write: true, - createNew: true, - mode: 0o626, - }); - const pathInfo = Deno.statSync(fileUrl); - if (Deno.build.os !== "windows") { - assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask()); - } - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { write: false } }, - async function writePermFailure() { - const filename = "tests/hello.txt"; - const openOptions: Deno.OpenOptions[] = [{ write: true }, { append: true }]; - for (const options of openOptions) { - await assertRejects(async () => { - await Deno.open(filename, options); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test(async function openOptions() { - const filename = "cli/tests/testdata/assets/fixture.json"; - await assertRejects( - async () => { - await Deno.open(filename, { write: false }); - }, - Error, - "OpenOptions requires at least one option to be true", - ); - - await assertRejects( - async () => { - await Deno.open(filename, { truncate: true, write: false }); - }, - Error, - "'truncate' option requires 'write' option", - ); - - await assertRejects( - async () => { - await Deno.open(filename, { create: true, write: false }); - }, - Error, - "'create' or 'createNew' options require 'write' or 'append' option", - ); - - await assertRejects( - async () => { - await Deno.open(filename, { createNew: true, append: false }); - }, - Error, - "'create' or 'createNew' options require 'write' or 'append' option", - ); -}); - -Deno.test({ permissions: { read: false } }, async function readPermFailure() { - await assertRejects(async () => { - await Deno.open("package.json", { read: true }); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { permissions: { write: true } }, - async function writeNullBufferFailure() { - const tempDir = Deno.makeTempDirSync(); - const filename = tempDir + "hello.txt"; - const w = { - write: true, - truncate: true, - create: true, - }; - using file = await Deno.open(filename, w); - - // writing null should throw an error - await assertRejects( - async () => { - // deno-lint-ignore no-explicit-any - await file.write(null as any); - }, - ); // TODO(bartlomieju): Check error kind when dispatch_minimal pipes errors properly - await Deno.remove(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { write: true, read: true } }, - async function readNullBufferFailure() { - const tempDir = Deno.makeTempDirSync(); - const filename = tempDir + "hello.txt"; - using file = await Deno.open(filename, { - read: true, - write: true, - truncate: true, - create: true, - }); - - // reading into an empty buffer should return 0 immediately - const bytesRead = await file.read(new Uint8Array(0)); - assert(bytesRead === 0); - - // reading file into null buffer should throw an error - await assertRejects(async () => { - // deno-lint-ignore no-explicit-any - await file.read(null as any); - }, TypeError); - // TODO(bartlomieju): Check error kind when dispatch_minimal pipes errors properly - - await Deno.remove(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { write: false, read: false } }, - async function readWritePermFailure() { - const filename = "tests/hello.txt"; - await assertRejects(async () => { - await Deno.open(filename, { read: true }); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { write: true, read: true } }, - async function openNotFound() { - await assertRejects( - async () => { - await Deno.open("bad_file_name"); - }, - Deno.errors.NotFound, - `open 'bad_file_name'`, - ); - }, -); - -Deno.test( - { permissions: { write: true, read: true } }, - function openSyncNotFound() { - assertThrows( - () => { - Deno.openSync("bad_file_name"); - }, - Deno.errors.NotFound, - `open 'bad_file_name'`, - ); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function createFile() { - const tempDir = await Deno.makeTempDir(); - const filename = tempDir + "/test.txt"; - const f = await Deno.create(filename); - let fileInfo = Deno.statSync(filename); - assert(fileInfo.isFile); - assert(fileInfo.size === 0); - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - await f.write(data); - fileInfo = Deno.statSync(filename); - assert(fileInfo.size === 5); - f.close(); - - // TODO(bartlomieju): test different modes - await Deno.remove(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function createFileWithUrl() { - const tempDir = await Deno.makeTempDir(); - const fileUrl = new URL( - `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`, - ); - const f = await Deno.create(fileUrl); - let fileInfo = Deno.statSync(fileUrl); - assert(fileInfo.isFile); - assert(fileInfo.size === 0); - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - await f.write(data); - fileInfo = Deno.statSync(fileUrl); - assert(fileInfo.size === 5); - f.close(); - - await Deno.remove(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function createSyncFile() { - const tempDir = await Deno.makeTempDir(); - const filename = tempDir + "/test.txt"; - const f = Deno.createSync(filename); - let fileInfo = Deno.statSync(filename); - assert(fileInfo.isFile); - assert(fileInfo.size === 0); - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - await f.write(data); - fileInfo = Deno.statSync(filename); - assert(fileInfo.size === 5); - f.close(); - - // TODO(bartlomieju): test different modes - await Deno.remove(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function createSyncFileWithUrl() { - const tempDir = await Deno.makeTempDir(); - const fileUrl = new URL( - `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`, - ); - const f = Deno.createSync(fileUrl); - let fileInfo = Deno.statSync(fileUrl); - assert(fileInfo.isFile); - assert(fileInfo.size === 0); - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - await f.write(data); - fileInfo = Deno.statSync(fileUrl); - assert(fileInfo.size === 5); - f.close(); - - await Deno.remove(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function openModeWrite() { - const tempDir = Deno.makeTempDirSync(); - const encoder = new TextEncoder(); - const filename = tempDir + "hello.txt"; - const data = encoder.encode("Hello world!\n"); - let file = await Deno.open(filename, { - create: true, - write: true, - truncate: true, - }); - // assert file was created - let fileInfo = Deno.statSync(filename); - assert(fileInfo.isFile); - assertEquals(fileInfo.size, 0); - // write some data - await file.write(data); - fileInfo = Deno.statSync(filename); - assertEquals(fileInfo.size, 13); - // assert we can't read from file - let thrown = false; - try { - const buf = new Uint8Array(20); - await file.read(buf); - } catch (_e) { - thrown = true; - } finally { - assert(thrown, "'w' mode shouldn't allow to read file"); - } - file.close(); - // assert that existing file is truncated on open - file = await Deno.open(filename, { - write: true, - truncate: true, - }); - file.close(); - const fileSize = Deno.statSync(filename).size; - assertEquals(fileSize, 0); - await Deno.remove(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function openModeWriteRead() { - const tempDir = Deno.makeTempDirSync(); - const encoder = new TextEncoder(); - const filename = tempDir + "hello.txt"; - const data = encoder.encode("Hello world!\n"); - - using file = await Deno.open(filename, { - write: true, - truncate: true, - create: true, - read: true, - }); - const seekPosition = 0; - // assert file was created - let fileInfo = Deno.statSync(filename); - assert(fileInfo.isFile); - assertEquals(fileInfo.size, 0); - // write some data - await file.write(data); - fileInfo = Deno.statSync(filename); - assertEquals(fileInfo.size, 13); - - const buf = new Uint8Array(20); - // seeking from beginning of a file - const cursorPosition = await file.seek(seekPosition, Deno.SeekMode.Start); - assertEquals(seekPosition, cursorPosition); - const result = await file.read(buf); - assertEquals(result, 13); - - await Deno.remove(tempDir, { recursive: true }); - }, -); - -Deno.test({ permissions: { read: true } }, async function seekStart() { - const filename = "cli/tests/testdata/assets/hello.txt"; - using file = await Deno.open(filename); - const seekPosition = 6; - // Deliberately move 1 step forward - await file.read(new Uint8Array(1)); // "H" - // Skipping "Hello " - // seeking from beginning of a file plus seekPosition - const cursorPosition = await file.seek(seekPosition, Deno.SeekMode.Start); - assertEquals(seekPosition, cursorPosition); - const buf = new Uint8Array(6); - await file.read(buf); - const decoded = new TextDecoder().decode(buf); - assertEquals(decoded, "world!"); -}); - -Deno.test({ permissions: { read: true } }, async function seekStartBigInt() { - const filename = "cli/tests/testdata/assets/hello.txt"; - using file = await Deno.open(filename); - const seekPosition = 6n; - // Deliberately move 1 step forward - await file.read(new Uint8Array(1)); // "H" - // Skipping "Hello " - // seeking from beginning of a file plus seekPosition - const cursorPosition = await file.seek(seekPosition, Deno.SeekMode.Start); - assertEquals(seekPosition, BigInt(cursorPosition)); - const buf = new Uint8Array(6); - await file.read(buf); - const decoded = new TextDecoder().decode(buf); - assertEquals(decoded, "world!"); -}); - -Deno.test({ permissions: { read: true } }, function seekSyncStart() { - const filename = "cli/tests/testdata/assets/hello.txt"; - using file = Deno.openSync(filename); - const seekPosition = 6; - // Deliberately move 1 step forward - file.readSync(new Uint8Array(1)); // "H" - // Skipping "Hello " - // seeking from beginning of a file plus seekPosition - const cursorPosition = file.seekSync(seekPosition, Deno.SeekMode.Start); - assertEquals(seekPosition, cursorPosition); - const buf = new Uint8Array(6); - file.readSync(buf); - const decoded = new TextDecoder().decode(buf); - assertEquals(decoded, "world!"); -}); - -Deno.test({ permissions: { read: true } }, async function seekCurrent() { - const filename = "cli/tests/testdata/assets/hello.txt"; - using file = await Deno.open(filename); - // Deliberately move 1 step forward - await file.read(new Uint8Array(1)); // "H" - // Skipping "ello " - const seekPosition = 5; - // seekPosition is relative to current cursor position after read - const cursorPosition = await file.seek(seekPosition, Deno.SeekMode.Current); - assertEquals(seekPosition + 1, cursorPosition); - const buf = new Uint8Array(6); - await file.read(buf); - const decoded = new TextDecoder().decode(buf); - assertEquals(decoded, "world!"); -}); - -Deno.test({ permissions: { read: true } }, function seekSyncCurrent() { - const filename = "cli/tests/testdata/assets/hello.txt"; - using file = Deno.openSync(filename); - // Deliberately move 1 step forward - file.readSync(new Uint8Array(1)); // "H" - // Skipping "ello " - const seekPosition = 5; - // seekPosition is relative to current cursor position after read - const cursorPosition = file.seekSync(seekPosition, Deno.SeekMode.Current); - assertEquals(seekPosition + 1, cursorPosition); - const buf = new Uint8Array(6); - file.readSync(buf); - const decoded = new TextDecoder().decode(buf); - assertEquals(decoded, "world!"); -}); - -Deno.test({ permissions: { read: true } }, async function seekEnd() { - const filename = "cli/tests/testdata/assets/hello.txt"; - using file = await Deno.open(filename); - const seekPosition = -6; - // seek from end of file that has 12 chars, 12 - 6 = 6 - const cursorPosition = await file.seek(seekPosition, Deno.SeekMode.End); - assertEquals(6, cursorPosition); - const buf = new Uint8Array(6); - await file.read(buf); - const decoded = new TextDecoder().decode(buf); - assertEquals(decoded, "world!"); -}); - -Deno.test({ permissions: { read: true } }, function seekSyncEnd() { - const filename = "cli/tests/testdata/assets/hello.txt"; - using file = Deno.openSync(filename); - const seekPosition = -6; - // seek from end of file that has 12 chars, 12 - 6 = 6 - const cursorPosition = file.seekSync(seekPosition, Deno.SeekMode.End); - assertEquals(6, cursorPosition); - const buf = new Uint8Array(6); - file.readSync(buf); - const decoded = new TextDecoder().decode(buf); - assertEquals(decoded, "world!"); -}); - -Deno.test({ permissions: { read: true } }, async function seekMode() { - const filename = "cli/tests/testdata/assets/hello.txt"; - using file = await Deno.open(filename); - await assertRejects( - async () => { - await file.seek(1, -1 as unknown as Deno.SeekMode); - }, - TypeError, - "Invalid seek mode", - ); - - // We should still be able to read the file - // since it is still open. - const buf = new Uint8Array(1); - await file.read(buf); // "H" - assertEquals(new TextDecoder().decode(buf), "H"); -}); - -Deno.test( - { permissions: { read: true, write: true } }, - function fileTruncateSyncSuccess() { - const filename = Deno.makeTempDirSync() + "/test_fileTruncateSync.txt"; - using file = Deno.openSync(filename, { - create: true, - read: true, - write: true, - }); - - file.truncateSync(20); - assertEquals(Deno.readFileSync(filename).byteLength, 20); - file.truncateSync(5); - assertEquals(Deno.readFileSync(filename).byteLength, 5); - file.truncateSync(-5); - assertEquals(Deno.readFileSync(filename).byteLength, 0); - - Deno.removeSync(filename); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function fileTruncateSuccess() { - const filename = Deno.makeTempDirSync() + "/test_fileTruncate.txt"; - using file = await Deno.open(filename, { - create: true, - read: true, - write: true, - }); - - await file.truncate(20); - assertEquals((await Deno.readFile(filename)).byteLength, 20); - await file.truncate(5); - assertEquals((await Deno.readFile(filename)).byteLength, 5); - await file.truncate(-5); - assertEquals((await Deno.readFile(filename)).byteLength, 0); - - await Deno.remove(filename); - }, -); - -Deno.test({ permissions: { read: true } }, function fileStatSyncSuccess() { - using file = Deno.openSync("README.md"); - const fileInfo = file.statSync(); - assert(fileInfo.isFile); - assert(!fileInfo.isSymlink); - assert(!fileInfo.isDirectory); - assert(fileInfo.size); - assert(fileInfo.atime); - assert(fileInfo.mtime); - // The `birthtime` field is not available on Linux before kernel version 4.11. - assert(fileInfo.birthtime || Deno.build.os === "linux"); -}); - -Deno.test(async function fileStatSuccess() { - using file = await Deno.open("README.md"); - const fileInfo = await file.stat(); - assert(fileInfo.isFile); - assert(!fileInfo.isSymlink); - assert(!fileInfo.isDirectory); - assert(fileInfo.size); - assert(fileInfo.atime); - assert(fileInfo.mtime); - // The `birthtime` field is not available on Linux before kernel version 4.11. - assert(fileInfo.birthtime || Deno.build.os === "linux"); -}); - -Deno.test({ permissions: { read: true } }, async function readableStream() { - const filename = "cli/tests/testdata/assets/hello.txt"; - const file = await Deno.open(filename); - assert(file.readable instanceof ReadableStream); - const chunks = []; - for await (const chunk of file.readable) { - chunks.push(chunk); - } - assertEquals(chunks.length, 1); - assertEquals(chunks[0].byteLength, 12); -}); - -Deno.test( - { permissions: { read: true } }, - async function readableStreamTextEncoderPipe() { - const filename = "cli/tests/testdata/assets/hello.txt"; - const file = await Deno.open(filename); - const readable = file.readable.pipeThrough(new TextDecoderStream()); - const chunks = []; - for await (const chunk of readable) { - chunks.push(chunk); - } - assertEquals(chunks.length, 1); - assertEquals(chunks[0].length, 12); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writableStream() { - const path = await Deno.makeTempFile(); - const file = await Deno.open(path, { write: true }); - assert(file.writable instanceof WritableStream); - const readable = new ReadableStream({ - start(controller) { - controller.enqueue(new TextEncoder().encode("hello ")); - controller.enqueue(new TextEncoder().encode("world!")); - controller.close(); - }, - }); - await readable.pipeTo(file.writable); - const res = await Deno.readTextFile(path); - assertEquals(res, "hello world!"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function readTextFileNonUtf8() { - const path = await Deno.makeTempFile(); - using file = await Deno.open(path, { write: true }); - await file.write(new TextEncoder().encode("hello ")); - await file.write(new Uint8Array([0xC0])); - - const res = await Deno.readTextFile(path); - const resSync = Deno.readTextFileSync(path); - assertEquals(res, resSync); - assertEquals(res, "hello \uFFFD"); - }, -); - -Deno.test( - { permissions: { read: true } }, - async function fsFileExplicitResourceManagement() { - let file2: Deno.FsFile; - - { - using file = await Deno.open("cli/tests/testdata/assets/hello.txt"); - file2 = file; - - const stat = file.statSync(); - assert(stat.isFile); - } - - assertThrows(() => file2.statSync(), Deno.errors.BadResource); - }, -); - -Deno.test( - { permissions: { read: true } }, - async function fsFileExplicitResourceManagementManualClose() { - using file = await Deno.open("cli/tests/testdata/assets/hello.txt"); - file.close(); - assertThrows(() => file.statSync(), Deno.errors.BadResource); // definitely closed - // calling [Symbol.dispose] after manual close is a no-op - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function fsFileDatasyncSyncSuccess() { - const filename = Deno.makeTempDirSync() + "/test_fdatasyncSync.txt"; - const file = Deno.openSync(filename, { - read: true, - write: true, - create: true, - }); - const data = new Uint8Array(64); - file.writeSync(data); - file.syncDataSync(); - assertEquals(Deno.readFileSync(filename), data); - file.close(); - Deno.removeSync(filename); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function fsFileDatasyncSuccess() { - const filename = (await Deno.makeTempDir()) + "/test_fdatasync.txt"; - const file = await Deno.open(filename, { - read: true, - write: true, - create: true, - }); - const data = new Uint8Array(64); - await file.write(data); - await file.syncData(); - assertEquals(await Deno.readFile(filename), data); - file.close(); - await Deno.remove(filename); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function fsFileSyncSyncSuccess() { - const filename = Deno.makeTempDirSync() + "/test_fsyncSync.txt"; - const file = Deno.openSync(filename, { - read: true, - write: true, - create: true, - }); - const size = 64; - file.truncateSync(size); - file.syncSync(); - assertEquals(file.statSync().size, size); - file.close(); - Deno.removeSync(filename); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function fsFileSyncSuccess() { - const filename = (await Deno.makeTempDir()) + "/test_fsync.txt"; - const file = await Deno.open(filename, { - read: true, - write: true, - create: true, - }); - const size = 64; - await file.truncate(size); - await file.sync(); - assertEquals((await file.stat()).size, size); - file.close(); - await Deno.remove(filename); - }, -); - -Deno.test( - { permissions: { read: true, run: true, hrtime: true } }, - async function fsFileLockFileSync() { - await runFlockTests({ sync: true }); - }, -); - -Deno.test( - { permissions: { read: true, run: true, hrtime: true } }, - async function fsFileLockFileAsync() { - await runFlockTests({ sync: false }); - }, -); - -async function runFlockTests(opts: { sync: boolean }) { - assertEquals( - await checkFirstBlocksSecond({ - firstExclusive: true, - secondExclusive: false, - sync: opts.sync, - }), - true, - "exclusive blocks shared", - ); - assertEquals( - await checkFirstBlocksSecond({ - firstExclusive: false, - secondExclusive: true, - sync: opts.sync, - }), - true, - "shared blocks exclusive", - ); - assertEquals( - await checkFirstBlocksSecond({ - firstExclusive: true, - secondExclusive: true, - sync: opts.sync, - }), - true, - "exclusive blocks exclusive", - ); - assertEquals( - await checkFirstBlocksSecond({ - firstExclusive: false, - secondExclusive: false, - sync: opts.sync, - // need to wait for both to enter the lock to prevent the case where the - // first process enters and exits the lock before the second even enters - waitBothEnteredLock: true, - }), - false, - "shared does not block shared", - ); -} - -async function checkFirstBlocksSecond(opts: { - firstExclusive: boolean; - secondExclusive: boolean; - sync: boolean; - waitBothEnteredLock?: boolean; -}) { - const firstProcess = runFlockTestProcess({ - exclusive: opts.firstExclusive, - sync: opts.sync, - }); - const secondProcess = runFlockTestProcess({ - exclusive: opts.secondExclusive, - sync: opts.sync, - }); - try { - const sleep = (time: number) => new Promise((r) => setTimeout(r, time)); - - await Promise.all([ - firstProcess.waitStartup(), - secondProcess.waitStartup(), - ]); - - await firstProcess.enterLock(); - await firstProcess.waitEnterLock(); - - await secondProcess.enterLock(); - await sleep(100); - - if (!opts.waitBothEnteredLock) { - await firstProcess.exitLock(); - } - - await secondProcess.waitEnterLock(); - - if (opts.waitBothEnteredLock) { - await firstProcess.exitLock(); - } - - await secondProcess.exitLock(); - - // collect the final output - const firstPsTimes = await firstProcess.getTimes(); - const secondPsTimes = await secondProcess.getTimes(); - return firstPsTimes.exitTime < secondPsTimes.enterTime; - } finally { - await firstProcess.close(); - await secondProcess.close(); - } -} - -function runFlockTestProcess(opts: { exclusive: boolean; sync: boolean }) { - const path = "cli/tests/testdata/assets/lock_target.txt"; - const scriptText = ` - const file = Deno.openSync("${path}"); - - // ready signal - Deno.stdout.writeSync(new Uint8Array(1)); - // wait for enter lock signal - Deno.stdin.readSync(new Uint8Array(1)); - - // entering signal - Deno.stdout.writeSync(new Uint8Array(1)); - // lock and record the entry time - ${ - opts.sync - ? `file.lockSync(${opts.exclusive ? "true" : "false"});` - : `await file.lock(${opts.exclusive ? "true" : "false"});` - } - const enterTime = new Date().getTime(); - // entered signal - Deno.stdout.writeSync(new Uint8Array(1)); - - // wait for exit lock signal - Deno.stdin.readSync(new Uint8Array(1)); - - // record the exit time and wait a little bit before releasing - // the lock so that the enter time of the next process doesn't - // occur at the same time as this exit time - const exitTime = new Date().getTime(); - await new Promise(resolve => setTimeout(resolve, 100)); - - // release the lock - ${opts.sync ? "file.unlockSync();" : "await file.unlock();"} - - // exited signal - Deno.stdout.writeSync(new Uint8Array(1)); - - // output the enter and exit time - console.log(JSON.stringify({ enterTime, exitTime })); -`; - - const process = new Deno.Command(Deno.execPath(), { - args: ["eval", "--unstable", scriptText], - stdin: "piped", - stdout: "piped", - stderr: "null", - }).spawn(); - - const waitSignal = async () => { - const reader = process.stdout.getReader({ mode: "byob" }); - await reader.read(new Uint8Array(1)); - reader.releaseLock(); - }; - const signal = async () => { - const writer = process.stdin.getWriter(); - await writer.write(new Uint8Array(1)); - writer.releaseLock(); - }; - - return { - async waitStartup() { - await waitSignal(); - }, - async enterLock() { - await signal(); - await waitSignal(); // entering signal - }, - async waitEnterLock() { - await waitSignal(); - }, - async exitLock() { - await signal(); - await waitSignal(); - }, - getTimes: async () => { - const { stdout } = await process.output(); - const text = new TextDecoder().decode(stdout); - return JSON.parse(text) as { - enterTime: number; - exitTime: number; - }; - }, - close: async () => { - await process.status; - await process.stdin.close(); - }, - }; -} diff --git a/cli/tests/unit/flock_test.ts b/cli/tests/unit/flock_test.ts deleted file mode 100644 index 3189b4a56..000000000 --- a/cli/tests/unit/flock_test.ts +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "./test_util.ts"; - -Deno.test( - { permissions: { read: true, run: true, hrtime: true } }, - async function flockFileSync() { - await runFlockTests({ sync: true }); - }, -); - -Deno.test( - { permissions: { read: true, run: true, hrtime: true } }, - async function flockFileAsync() { - await runFlockTests({ sync: false }); - }, -); - -async function runFlockTests(opts: { sync: boolean }) { - assertEquals( - await checkFirstBlocksSecond({ - firstExclusive: true, - secondExclusive: false, - sync: opts.sync, - }), - true, - "exclusive blocks shared", - ); - assertEquals( - await checkFirstBlocksSecond({ - firstExclusive: false, - secondExclusive: true, - sync: opts.sync, - }), - true, - "shared blocks exclusive", - ); - assertEquals( - await checkFirstBlocksSecond({ - firstExclusive: true, - secondExclusive: true, - sync: opts.sync, - }), - true, - "exclusive blocks exclusive", - ); - assertEquals( - await checkFirstBlocksSecond({ - firstExclusive: false, - secondExclusive: false, - sync: opts.sync, - // need to wait for both to enter the lock to prevent the case where the - // first process enters and exits the lock before the second even enters - waitBothEnteredLock: true, - }), - false, - "shared does not block shared", - ); -} - -async function checkFirstBlocksSecond(opts: { - firstExclusive: boolean; - secondExclusive: boolean; - sync: boolean; - waitBothEnteredLock?: boolean; -}) { - const firstProcess = runFlockTestProcess({ - exclusive: opts.firstExclusive, - sync: opts.sync, - }); - const secondProcess = runFlockTestProcess({ - exclusive: opts.secondExclusive, - sync: opts.sync, - }); - try { - const sleep = (time: number) => new Promise((r) => setTimeout(r, time)); - - await Promise.all([ - firstProcess.waitStartup(), - secondProcess.waitStartup(), - ]); - - await firstProcess.enterLock(); - await firstProcess.waitEnterLock(); - - await secondProcess.enterLock(); - await sleep(100); - - if (!opts.waitBothEnteredLock) { - await firstProcess.exitLock(); - } - - await secondProcess.waitEnterLock(); - - if (opts.waitBothEnteredLock) { - await firstProcess.exitLock(); - } - - await secondProcess.exitLock(); - - // collect the final output - const firstPsTimes = await firstProcess.getTimes(); - const secondPsTimes = await secondProcess.getTimes(); - return firstPsTimes.exitTime < secondPsTimes.enterTime; - } finally { - await firstProcess.close(); - await secondProcess.close(); - } -} - -function runFlockTestProcess(opts: { exclusive: boolean; sync: boolean }) { - const path = "cli/tests/testdata/assets/lock_target.txt"; - const scriptText = ` - const { rid } = Deno.openSync("${path}"); - - // ready signal - Deno.stdout.writeSync(new Uint8Array(1)); - // wait for enter lock signal - Deno.stdin.readSync(new Uint8Array(1)); - - // entering signal - Deno.stdout.writeSync(new Uint8Array(1)); - // lock and record the entry time - ${ - opts.sync - ? `Deno.flockSync(rid, ${opts.exclusive ? "true" : "false"});` - : `await Deno.flock(rid, ${opts.exclusive ? "true" : "false"});` - } - const enterTime = new Date().getTime(); - // entered signal - Deno.stdout.writeSync(new Uint8Array(1)); - - // wait for exit lock signal - Deno.stdin.readSync(new Uint8Array(1)); - - // record the exit time and wait a little bit before releasing - // the lock so that the enter time of the next process doesn't - // occur at the same time as this exit time - const exitTime = new Date().getTime(); - await new Promise(resolve => setTimeout(resolve, 100)); - - // release the lock - ${opts.sync ? "Deno.funlockSync(rid);" : "await Deno.funlock(rid);"} - - // exited signal - Deno.stdout.writeSync(new Uint8Array(1)); - - // output the enter and exit time - console.log(JSON.stringify({ enterTime, exitTime })); -`; - - const process = new Deno.Command(Deno.execPath(), { - args: ["eval", "--unstable", scriptText], - stdin: "piped", - stdout: "piped", - stderr: "null", - }).spawn(); - - const waitSignal = async () => { - const reader = process.stdout.getReader({ mode: "byob" }); - await reader.read(new Uint8Array(1)); - reader.releaseLock(); - }; - const signal = async () => { - const writer = process.stdin.getWriter(); - await writer.write(new Uint8Array(1)); - writer.releaseLock(); - }; - - return { - async waitStartup() { - await waitSignal(); - }, - async enterLock() { - await signal(); - await waitSignal(); // entering signal - }, - async waitEnterLock() { - await waitSignal(); - }, - async exitLock() { - await signal(); - await waitSignal(); - }, - getTimes: async () => { - const { stdout } = await process.output(); - const text = new TextDecoder().decode(stdout); - return JSON.parse(text) as { - enterTime: number; - exitTime: number; - }; - }, - close: async () => { - await process.status; - await process.stdin.close(); - }, - }; -} diff --git a/cli/tests/unit/fs_events_test.ts b/cli/tests/unit/fs_events_test.ts deleted file mode 100644 index 4f7cdc4d5..000000000 --- a/cli/tests/unit/fs_events_test.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assert, assertEquals, assertThrows, delay } from "./test_util.ts"; - -// TODO(ry) Add more tests to specify format. - -Deno.test({ permissions: { read: false } }, function watchFsPermissions() { - assertThrows(() => { - Deno.watchFs("."); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, function watchFsInvalidPath() { - if (Deno.build.os === "windows") { - assertThrows( - () => { - Deno.watchFs("non-existent.file"); - }, - Error, - "Input watch path is neither a file nor a directory", - ); - } else { - assertThrows(() => { - Deno.watchFs("non-existent.file"); - }, Deno.errors.NotFound); - } -}); - -async function getTwoEvents( - iter: Deno.FsWatcher, -): Promise<Deno.FsEvent[]> { - const events = []; - for await (const event of iter) { - events.push(event); - if (events.length > 2) break; - } - return events; -} - -async function makeTempDir(): Promise<string> { - const testDir = await Deno.makeTempDir(); - // The watcher sometimes witnesses the creation of it's own root - // directory. Delay a bit. - await delay(100); - return testDir; -} - -Deno.test( - { permissions: { read: true, write: true } }, - async function watchFsBasic() { - const testDir = await makeTempDir(); - const iter = Deno.watchFs(testDir); - - // Asynchronously capture two fs events. - const eventsPromise = getTwoEvents(iter); - - // Make some random file system activity. - const file1 = testDir + "/file1.txt"; - const file2 = testDir + "/file2.txt"; - Deno.writeFileSync(file1, new Uint8Array([0, 1, 2])); - Deno.writeFileSync(file2, new Uint8Array([0, 1, 2])); - - // We should have gotten two fs events. - const events = await eventsPromise; - assert(events.length >= 2); - assert(events[0].kind == "create"); - assert(events[0].paths[0].includes(testDir)); - assert(events[1].kind == "create" || events[1].kind == "modify"); - assert(events[1].paths[0].includes(testDir)); - }, -); - -// TODO(kt3k): This test is for the backward compatibility of `.return` method. -// This should be removed at 2.0 -Deno.test( - { permissions: { read: true, write: true } }, - async function watchFsReturn() { - const testDir = await makeTempDir(); - const iter = Deno.watchFs(testDir); - - // Asynchronously loop events. - const eventsPromise = getTwoEvents(iter); - - // Close the watcher. - await iter.return!(); - - // Expect zero events. - const events = await eventsPromise; - assertEquals(events, []); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function watchFsClose() { - const testDir = await makeTempDir(); - const iter = Deno.watchFs(testDir); - - // Asynchronously loop events. - const eventsPromise = getTwoEvents(iter); - - // Close the watcher. - iter.close(); - - // Expect zero events. - const events = await eventsPromise; - assertEquals(events, []); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function watchFsExplicitResourceManagement() { - let res; - { - const testDir = await makeTempDir(); - using iter = Deno.watchFs(testDir); - - res = iter[Symbol.asyncIterator]().next(); - } - - const { done } = await res; - assert(done); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function watchFsExplicitResourceManagementManualClose() { - const testDir = await makeTempDir(); - using iter = Deno.watchFs(testDir); - - const res = iter[Symbol.asyncIterator]().next(); - - iter.close(); - const { done } = await res; - assert(done); - }, -); diff --git a/cli/tests/unit/get_random_values_test.ts b/cli/tests/unit/get_random_values_test.ts deleted file mode 100644 index 75aaf4c1b..000000000 --- a/cli/tests/unit/get_random_values_test.ts +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertNotEquals, assertStrictEquals } from "./test_util.ts"; - -Deno.test(function getRandomValuesInt8Array() { - const arr = new Int8Array(32); - crypto.getRandomValues(arr); - assertNotEquals(arr, new Int8Array(32)); -}); - -Deno.test(function getRandomValuesUint8Array() { - const arr = new Uint8Array(32); - crypto.getRandomValues(arr); - assertNotEquals(arr, new Uint8Array(32)); -}); - -Deno.test(function getRandomValuesUint8ClampedArray() { - const arr = new Uint8ClampedArray(32); - crypto.getRandomValues(arr); - assertNotEquals(arr, new Uint8ClampedArray(32)); -}); - -Deno.test(function getRandomValuesInt16Array() { - const arr = new Int16Array(4); - crypto.getRandomValues(arr); - assertNotEquals(arr, new Int16Array(4)); -}); - -Deno.test(function getRandomValuesUint16Array() { - const arr = new Uint16Array(4); - crypto.getRandomValues(arr); - assertNotEquals(arr, new Uint16Array(4)); -}); - -Deno.test(function getRandomValuesInt32Array() { - const arr = new Int32Array(8); - crypto.getRandomValues(arr); - assertNotEquals(arr, new Int32Array(8)); -}); - -Deno.test(function getRandomValuesBigInt64Array() { - const arr = new BigInt64Array(8); - crypto.getRandomValues(arr); - assertNotEquals(arr, new BigInt64Array(8)); -}); - -Deno.test(function getRandomValuesUint32Array() { - const arr = new Uint32Array(8); - crypto.getRandomValues(arr); - assertNotEquals(arr, new Uint32Array(8)); -}); - -Deno.test(function getRandomValuesBigUint64Array() { - const arr = new BigUint64Array(8); - crypto.getRandomValues(arr); - assertNotEquals(arr, new BigUint64Array(8)); -}); - -Deno.test(function getRandomValuesReturnValue() { - const arr = new Uint32Array(8); - const rtn = crypto.getRandomValues(arr); - assertNotEquals(arr, new Uint32Array(8)); - assertStrictEquals(rtn, arr); -}); diff --git a/cli/tests/unit/globals_test.ts b/cli/tests/unit/globals_test.ts deleted file mode 100644 index 00be3f451..000000000 --- a/cli/tests/unit/globals_test.ts +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -// deno-lint-ignore-file no-window-prefix -import { assert, assertEquals, assertRejects } from "./test_util.ts"; - -Deno.test(function globalThisExists() { - assert(globalThis != null); -}); - -Deno.test(function noInternalGlobals() { - // globalThis.__bootstrap should not be there. - for (const key of Object.keys(globalThis)) { - assert(!key.startsWith("_")); - } -}); - -Deno.test(function windowExists() { - assert(window != null); -}); - -Deno.test(function selfExists() { - assert(self != null); -}); - -Deno.test(function windowWindowExists() { - assert(window.window === window); -}); - -Deno.test(function windowSelfExists() { - assert(window.self === window); -}); - -Deno.test(function globalThisEqualsWindow() { - assert(globalThis === window); -}); - -Deno.test(function globalThisEqualsSelf() { - assert(globalThis === self); -}); - -Deno.test(function globalThisInstanceofWindow() { - assert(globalThis instanceof Window); -}); - -Deno.test(function globalThisConstructorLength() { - assert(globalThis.constructor.length === 0); -}); - -Deno.test(function globalThisInstanceofEventTarget() { - assert(globalThis instanceof EventTarget); -}); - -Deno.test(function navigatorInstanceofNavigator() { - // TODO(nayeemrmn): Add `Navigator` to deno_lint globals. - // deno-lint-ignore no-undef - assert(navigator instanceof Navigator); -}); - -Deno.test(function DenoNamespaceExists() { - assert(Deno != null); -}); - -Deno.test(function DenoNamespaceEqualsWindowDeno() { - assert(Deno === window.Deno); -}); - -Deno.test(function DenoNamespaceIsNotFrozen() { - assert(!Object.isFrozen(Deno)); -}); - -Deno.test(function webAssemblyExists() { - assert(typeof WebAssembly.compile === "function"); -}); - -// @ts-ignore This is not publicly typed namespace, but it's there for sure. -const core = Deno[Deno.internal].core; - -Deno.test(function DenoNamespaceConfigurable() { - const desc = Object.getOwnPropertyDescriptor(globalThis, "Deno"); - assert(desc); - assert(desc.configurable); - assert(!desc.writable); -}); - -Deno.test(function DenoCoreNamespaceIsImmutable() { - const { print } = core; - try { - core.print = 1; - } catch { - // pass - } - assert(print === core.print); - try { - delete core.print; - } catch { - // pass - } - assert(print === core.print); -}); - -Deno.test(async function windowQueueMicrotask() { - let resolve1: () => void | undefined; - let resolve2: () => void | undefined; - let microtaskDone = false; - const p1 = new Promise<void>((res) => { - resolve1 = () => { - microtaskDone = true; - res(); - }; - }); - const p2 = new Promise<void>((res) => { - resolve2 = () => { - assert(microtaskDone); - res(); - }; - }); - window.queueMicrotask(resolve1!); - setTimeout(resolve2!, 0); - await p1; - await p2; -}); - -Deno.test(function webApiGlobalThis() { - assert(globalThis.FormData !== null); - assert(globalThis.TextEncoder !== null); - assert(globalThis.TextEncoderStream !== null); - assert(globalThis.TextDecoder !== null); - assert(globalThis.TextDecoderStream !== null); - assert(globalThis.CountQueuingStrategy !== null); - assert(globalThis.ByteLengthQueuingStrategy !== null); -}); - -Deno.test(function windowNameIsDefined() { - assertEquals(typeof globalThis.name, "string"); - assertEquals(name, ""); - assertEquals(window.name, name); - name = "foobar"; - assertEquals(window.name, "foobar"); - assertEquals(name, "foobar"); - name = ""; - assertEquals(window.name, ""); - assertEquals(name, ""); -}); - -Deno.test(async function promiseWithResolvers() { - { - const { promise, resolve } = Promise.withResolvers(); - resolve(true); - assert(await promise); - } - { - const { promise, reject } = Promise.withResolvers(); - reject(new Error("boom!")); - await assertRejects(() => promise, Error, "boom!"); - } -}); - -Deno.test(async function arrayFromAsync() { - // Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fromAsync#examples - // Thank you. - const asyncIterable = (async function* () { - for (let i = 0; i < 5; i++) { - await new Promise((resolve) => setTimeout(resolve, 10 * i)); - yield i; - } - })(); - - const a = await Array.fromAsync(asyncIterable); - assertEquals(a, [0, 1, 2, 3, 4]); - - const b = await Array.fromAsync(new Map([[1, 2], [3, 4]])); - assertEquals(b, [[1, 2], [3, 4]]); -}); - -// Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy#examples -Deno.test(function objectGroupBy() { - const inventory = [ - { name: "asparagus", type: "vegetables", quantity: 5 }, - { name: "bananas", type: "fruit", quantity: 0 }, - { name: "goat", type: "meat", quantity: 23 }, - { name: "cherries", type: "fruit", quantity: 5 }, - { name: "fish", type: "meat", quantity: 22 }, - ]; - const result = Object.groupBy(inventory, ({ type }) => type); - assertEquals(result, { - vegetables: [ - { name: "asparagus", type: "vegetables", quantity: 5 }, - ], - fruit: [ - { name: "bananas", type: "fruit", quantity: 0 }, - { name: "cherries", type: "fruit", quantity: 5 }, - ], - meat: [ - { name: "goat", type: "meat", quantity: 23 }, - { name: "fish", type: "meat", quantity: 22 }, - ], - }); -}); - -Deno.test(function objectGroupByEmpty() { - const empty: string[] = []; - const result = Object.groupBy(empty, () => "abc"); - assertEquals(result.abc, undefined); -}); - -// Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/groupBy#examples -Deno.test(function mapGroupBy() { - const inventory = [ - { name: "asparagus", type: "vegetables", quantity: 9 }, - { name: "bananas", type: "fruit", quantity: 5 }, - { name: "goat", type: "meat", quantity: 23 }, - { name: "cherries", type: "fruit", quantity: 12 }, - { name: "fish", type: "meat", quantity: 22 }, - ]; - const restock = { restock: true }; - const sufficient = { restock: false }; - const result = Map.groupBy( - inventory, - ({ quantity }) => quantity < 6 ? restock : sufficient, - ); - assertEquals(result.get(restock), [{ - name: "bananas", - type: "fruit", - quantity: 5, - }]); -}); diff --git a/cli/tests/unit/headers_test.ts b/cli/tests/unit/headers_test.ts deleted file mode 100644 index ad453b67f..000000000 --- a/cli/tests/unit/headers_test.ts +++ /dev/null @@ -1,416 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals, assertThrows } from "./test_util.ts"; -const { - inspectArgs, - // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol -} = Deno[Deno.internal]; - -Deno.test(function headersHasCorrectNameProp() { - assertEquals(Headers.name, "Headers"); -}); - -// Logic heavily copied from web-platform-tests, make -// sure pass mostly header basic test -// ref: https://github.com/web-platform-tests/wpt/blob/7c50c216081d6ea3c9afe553ee7b64534020a1b2/fetch/api/headers/headers-basic.html -Deno.test(function newHeaderTest() { - new Headers(); - new Headers(undefined); - new Headers({}); - try { - // deno-lint-ignore no-explicit-any - new Headers(null as any); - } catch (e) { - assert(e instanceof TypeError); - } -}); - -const headerDict: Record<string, string> = { - name1: "value1", - name2: "value2", - name3: "value3", - // deno-lint-ignore no-explicit-any - name4: undefined as any, - "Content-Type": "value4", -}; -// deno-lint-ignore no-explicit-any -const headerSeq: any[] = []; -for (const [name, value] of Object.entries(headerDict)) { - headerSeq.push([name, value]); -} - -Deno.test(function newHeaderWithSequence() { - const headers = new Headers(headerSeq); - for (const [name, value] of Object.entries(headerDict)) { - assertEquals(headers.get(name), String(value)); - } - assertEquals(headers.get("length"), null); -}); - -Deno.test(function newHeaderWithRecord() { - const headers = new Headers(headerDict); - for (const [name, value] of Object.entries(headerDict)) { - assertEquals(headers.get(name), String(value)); - } -}); - -Deno.test(function newHeaderWithHeadersInstance() { - const headers = new Headers(headerDict); - const headers2 = new Headers(headers); - for (const [name, value] of Object.entries(headerDict)) { - assertEquals(headers2.get(name), String(value)); - } -}); - -Deno.test(function headerAppendSuccess() { - const headers = new Headers(); - for (const [name, value] of Object.entries(headerDict)) { - headers.append(name, value); - assertEquals(headers.get(name), String(value)); - } -}); - -Deno.test(function headerSetSuccess() { - const headers = new Headers(); - for (const [name, value] of Object.entries(headerDict)) { - headers.set(name, value); - assertEquals(headers.get(name), String(value)); - } -}); - -Deno.test(function headerHasSuccess() { - const headers = new Headers(headerDict); - for (const name of Object.keys(headerDict)) { - assert(headers.has(name), "headers has name " + name); - assert( - !headers.has("nameNotInHeaders"), - "headers do not have header: nameNotInHeaders", - ); - } -}); - -Deno.test(function headerDeleteSuccess() { - const headers = new Headers(headerDict); - for (const name of Object.keys(headerDict)) { - assert(headers.has(name), "headers have a header: " + name); - headers.delete(name); - assert(!headers.has(name), "headers do not have anymore a header: " + name); - } -}); - -Deno.test(function headerGetSuccess() { - const headers = new Headers(headerDict); - for (const [name, value] of Object.entries(headerDict)) { - assertEquals(headers.get(name), String(value)); - assertEquals(headers.get("nameNotInHeaders"), null); - } -}); - -Deno.test(function headerEntriesSuccess() { - const headers = new Headers(headerDict); - const iterators = headers.entries(); - for (const it of iterators) { - const key = it[0]; - const value = it[1]; - assert(headers.has(key)); - assertEquals(value, headers.get(key)); - } -}); - -Deno.test(function headerKeysSuccess() { - const headers = new Headers(headerDict); - const iterators = headers.keys(); - for (const it of iterators) { - assert(headers.has(it)); - } -}); - -Deno.test(function headerValuesSuccess() { - const headers = new Headers(headerDict); - const iterators = headers.values(); - const entries = headers.entries(); - const values = []; - for (const pair of entries) { - values.push(pair[1]); - } - for (const it of iterators) { - assert(values.includes(it)); - } -}); - -const headerEntriesDict: Record<string, string> = { - name1: "value1", - Name2: "value2", - name: "value3", - "content-Type": "value4", - "Content-Typ": "value5", - "Content-Types": "value6", -}; - -Deno.test(function headerForEachSuccess() { - const headers = new Headers(headerEntriesDict); - const keys = Object.keys(headerEntriesDict); - keys.forEach((key) => { - const value = headerEntriesDict[key]; - const newkey = key.toLowerCase(); - headerEntriesDict[newkey] = value; - }); - let callNum = 0; - headers.forEach((value, key, container) => { - assertEquals(headers, container); - assertEquals(value, headerEntriesDict[key]); - callNum++; - }); - assertEquals(callNum, keys.length); -}); - -Deno.test(function headerSymbolIteratorSuccess() { - assert(Symbol.iterator in Headers.prototype); - const headers = new Headers(headerEntriesDict); - for (const header of headers) { - const key = header[0]; - const value = header[1]; - assert(headers.has(key)); - assertEquals(value, headers.get(key)); - } -}); - -Deno.test(function headerTypesAvailable() { - function newHeaders(): Headers { - return new Headers(); - } - const headers = newHeaders(); - assert(headers instanceof Headers); -}); - -// Modified from https://github.com/bitinn/node-fetch/blob/7d3293200a91ad52b5ca7962f9d6fd1c04983edb/test/test.js#L2001-L2014 -// Copyright (c) 2016 David Frank. MIT License. -Deno.test(function headerIllegalReject() { - let errorCount = 0; - try { - new Headers({ "He y": "ok" }); - } catch (_e) { - errorCount++; - } - try { - new Headers({ "Hé-y": "ok" }); - } catch (_e) { - errorCount++; - } - try { - new Headers({ "He-y": "ăk" }); - } catch (_e) { - errorCount++; - } - const headers = new Headers(); - try { - headers.append("Hé-y", "ok"); - } catch (_e) { - errorCount++; - } - try { - headers.delete("Hé-y"); - } catch (_e) { - errorCount++; - } - try { - headers.get("Hé-y"); - } catch (_e) { - errorCount++; - } - try { - headers.has("Hé-y"); - } catch (_e) { - errorCount++; - } - try { - headers.set("Hé-y", "ok"); - } catch (_e) { - errorCount++; - } - try { - headers.set("", "ok"); - } catch (_e) { - errorCount++; - } - assertEquals(errorCount, 9); - // 'o k' is valid value but invalid name - new Headers({ "He-y": "o k" }); -}); - -// If pair does not contain exactly two items,then throw a TypeError. -Deno.test(function headerParamsShouldThrowTypeError() { - let hasThrown = 0; - - try { - new Headers(([["1"]] as unknown) as Array<[string, string]>); - hasThrown = 1; - } catch (err) { - if (err instanceof TypeError) { - hasThrown = 2; - } else { - hasThrown = 3; - } - } - - assertEquals(hasThrown, 2); -}); - -Deno.test(function headerParamsArgumentsCheck() { - const methodRequireOneParam = ["delete", "get", "has", "forEach"] as const; - - const methodRequireTwoParams = ["append", "set"] as const; - - methodRequireOneParam.forEach((method) => { - const headers = new Headers(); - let hasThrown = 0; - try { - // deno-lint-ignore no-explicit-any - (headers as any)[method](); - hasThrown = 1; - } catch (err) { - if (err instanceof TypeError) { - hasThrown = 2; - } else { - hasThrown = 3; - } - } - assertEquals(hasThrown, 2); - }); - - methodRequireTwoParams.forEach((method) => { - const headers = new Headers(); - let hasThrown = 0; - - try { - // deno-lint-ignore no-explicit-any - (headers as any)[method](); - hasThrown = 1; - } catch (err) { - if (err instanceof TypeError) { - hasThrown = 2; - } else { - hasThrown = 3; - } - } - assertEquals(hasThrown, 2); - - hasThrown = 0; - try { - // deno-lint-ignore no-explicit-any - (headers as any)[method]("foo"); - hasThrown = 1; - } catch (err) { - if (err instanceof TypeError) { - hasThrown = 2; - } else { - hasThrown = 3; - } - } - assertEquals(hasThrown, 2); - }); -}); - -Deno.test(function headersInitMultiple() { - const headers = new Headers([ - ["Set-Cookie", "foo=bar"], - ["Set-Cookie", "bar=baz"], - ["X-Deno", "foo"], - ["X-Deno", "bar"], - ]); - const actual = [...headers]; - assertEquals(actual, [ - ["set-cookie", "foo=bar"], - ["set-cookie", "bar=baz"], - ["x-deno", "foo, bar"], - ]); -}); - -Deno.test(function headerInitWithPrototypePollution() { - const originalExec = RegExp.prototype.exec; - try { - RegExp.prototype.exec = () => { - throw Error(); - }; - new Headers([ - ["X-Deno", "foo"], - ["X-Deno", "bar"], - ]); - } finally { - RegExp.prototype.exec = originalExec; - } -}); - -Deno.test(function headersAppendMultiple() { - const headers = new Headers([ - ["Set-Cookie", "foo=bar"], - ["X-Deno", "foo"], - ]); - headers.append("set-Cookie", "bar=baz"); - headers.append("x-Deno", "bar"); - const actual = [...headers]; - assertEquals(actual, [ - ["set-cookie", "foo=bar"], - ["set-cookie", "bar=baz"], - ["x-deno", "foo, bar"], - ]); -}); - -Deno.test(function headersAppendDuplicateSetCookieKey() { - const headers = new Headers([["Set-Cookie", "foo=bar"]]); - headers.append("set-Cookie", "foo=baz"); - headers.append("Set-cookie", "baz=bar"); - const actual = [...headers]; - assertEquals(actual, [ - ["set-cookie", "foo=bar"], - ["set-cookie", "foo=baz"], - ["set-cookie", "baz=bar"], - ]); -}); - -Deno.test(function headersGetSetCookie() { - const headers = new Headers([ - ["Set-Cookie", "foo=bar"], - ["set-Cookie", "bar=qat"], - ]); - assertEquals(headers.get("SET-COOKIE"), "foo=bar, bar=qat"); -}); - -Deno.test(function toStringShouldBeWebCompatibility() { - const headers = new Headers(); - assertEquals(headers.toString(), "[object Headers]"); -}); - -function stringify(...args: unknown[]): string { - return inspectArgs(args).replace(/\n$/, ""); -} - -Deno.test(function customInspectReturnsCorrectHeadersFormat() { - const blankHeaders = new Headers(); - assertEquals(stringify(blankHeaders), "Headers {}"); - const singleHeader = new Headers([["Content-Type", "application/json"]]); - assertEquals( - stringify(singleHeader), - `Headers { "content-type": "application/json" }`, - ); - const multiParamHeader = new Headers([ - ["Content-Type", "application/json"], - ["Content-Length", "1337"], - ]); - assertEquals( - stringify(multiParamHeader), - `Headers { "content-length": "1337", "content-type": "application/json" }`, - ); -}); - -Deno.test(function invalidHeadersFlaky() { - assertThrows( - () => new Headers([["x", "\u0000x"]]), - TypeError, - "Header value is not valid.", - ); - assertThrows( - () => new Headers([["x", "\u0000x"]]), - TypeError, - "Header value is not valid.", - ); -}); diff --git a/cli/tests/unit/http_test.ts b/cli/tests/unit/http_test.ts deleted file mode 100644 index 66cc53113..000000000 --- a/cli/tests/unit/http_test.ts +++ /dev/null @@ -1,2801 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { Buffer, BufReader, BufWriter } from "@test_util/std/io/mod.ts"; -import { TextProtoReader } from "../testdata/run/textproto.ts"; -import { - assert, - assertEquals, - assertRejects, - assertStrictEquals, - assertThrows, - delay, - fail, -} from "./test_util.ts"; -import { join } from "@test_util/std/path/mod.ts"; - -const listenPort = 4507; -const listenPort2 = 4508; - -const { - buildCaseInsensitiveCommaValueFinder, - // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol -} = Deno[Deno.internal]; - -async function writeRequestAndReadResponse(conn: Deno.Conn): Promise<string> { - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - const w = new BufWriter(conn); - const r = new BufReader(conn); - const body = `GET / HTTP/1.1\r\nHost: 127.0.0.1:${listenPort}\r\n\r\n`; - const writeResult = await w.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await w.flush(); - const tpr = new TextProtoReader(r); - const statusLine = await tpr.readLine(); - assert(statusLine !== null); - const headers = await tpr.readMimeHeader(); - assert(headers !== null); - - const chunkedReader = chunkedBodyReader(headers, r); - const buf = new Uint8Array(5); - const dest = new Buffer(); - let result: number | null; - while ((result = await chunkedReader.read(buf)) !== null) { - const len = Math.min(buf.byteLength, result); - await dest.write(buf.subarray(0, len)); - } - return decoder.decode(dest.bytes()); -} - -Deno.test({ permissions: { net: true } }, async function httpServerBasic() { - let httpConn: Deno.HttpConn; - const promise = (async () => { - const listener = Deno.listen({ port: listenPort }); - const conn = await listener.accept(); - listener.close(); - httpConn = Deno.serveHttp(conn); - const reqEvent = await httpConn.nextRequest(); - assert(reqEvent); - const { request, respondWith } = reqEvent; - assertEquals(new URL(request.url).href, `http://127.0.0.1:${listenPort}/`); - assertEquals(await request.text(), ""); - await respondWith( - new Response("Hello World", { headers: { "foo": "bar" } }), - ); - })(); - - const resp = await fetch(`http://127.0.0.1:${listenPort}/`, { - headers: { "connection": "close" }, - }); - const clone = resp.clone(); - const text = await resp.text(); - assertEquals(text, "Hello World"); - assertEquals(resp.headers.get("foo"), "bar"); - const cloneText = await clone.text(); - assertEquals(cloneText, "Hello World"); - await promise; - - httpConn!.close(); -}); - -// https://github.com/denoland/deno/issues/15107 -Deno.test( - { permissions: { net: true } }, - async function httpLazyHeadersIssue15107() { - let headers: Headers; - const promise = (async () => { - const listener = Deno.listen({ port: 2333 }); - const conn = await listener.accept(); - listener.close(); - const httpConn = Deno.serveHttp(conn); - const e = await httpConn.nextRequest(); - assert(e); - const { request } = e; - request.text(); - headers = request.headers; - httpConn!.close(); - })(); - - const conn = await Deno.connect({ port: 2333 }); - // Send GET request with a body + content-length. - const encoder = new TextEncoder(); - const body = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: 5\r\n\r\n12345`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await promise; - conn.close(); - assertEquals(headers!.get("content-length"), "5"); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpReadHeadersAfterClose() { - const promise = (async () => { - const listener = Deno.listen({ port: 2334 }); - const conn = await listener.accept(); - listener.close(); - const httpConn = Deno.serveHttp(conn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - - await request.text(); // Read body - await respondWith(new Response("Hello World")); // Closes request - - assertThrows(() => request.headers, TypeError, "request closed"); - httpConn!.close(); - })(); - - const conn = await Deno.connect({ port: 2334 }); - // Send GET request with a body + content-length. - const encoder = new TextEncoder(); - const body = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: 5\r\n\r\n12345`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await promise; - conn.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerGetRequestBody() { - let httpConn: Deno.HttpConn; - const promise = (async () => { - const listener = Deno.listen({ port: listenPort }); - const conn = await listener.accept(); - listener.close(); - httpConn = Deno.serveHttp(conn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.body, null); - await respondWith(new Response("", { headers: {} })); - })(); - - const conn = await Deno.connect({ port: listenPort }); - // Send GET request with a body + content-length. - const encoder = new TextEncoder(); - const body = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:${listenPort}\r\nContent-Length: 5\r\n\r\n12345`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - const resp = new Uint8Array(200); - const readResult = await conn.read(resp); - assertEquals(readResult, 138); - - conn.close(); - - await promise; - httpConn!.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerStreamResponse() { - const stream = new TransformStream(); - const writer = stream.writable.getWriter(); - writer.write(new TextEncoder().encode("hello ")); - writer.write(new TextEncoder().encode("world")); - writer.close(); - - let httpConn: Deno.HttpConn; - const listener = Deno.listen({ port: listenPort }); - const promise = (async () => { - const conn = await listener.accept(); - httpConn = Deno.serveHttp(conn); - const evt = await httpConn.nextRequest(); - assert(evt); - const { request, respondWith } = evt; - assert(!request.body); - await respondWith(new Response(stream.readable)); - })(); - - const resp = await fetch(`http://127.0.0.1:${listenPort}/`); - const respBody = await resp.text(); - assertEquals("hello world", respBody); - await promise; - httpConn!.close(); - listener.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerStreamRequest() { - const stream = new TransformStream(); - const writer = stream.writable.getWriter(); - writer.write(new TextEncoder().encode("hello ")); - writer.write(new TextEncoder().encode("world")); - writer.close(); - - const listener = Deno.listen({ port: listenPort }); - const promise = (async () => { - const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); - const evt = await httpConn.nextRequest(); - assert(evt); - const { request, respondWith } = evt; - const reqBody = await request.text(); - assertEquals("hello world", reqBody); - await respondWith(new Response("")); - - // TODO(ry) If we don't call httpConn.nextRequest() here we get "error sending - // request for url (https://localhost:${listenPort}/): connection closed before - // message completed". - assertEquals(await httpConn.nextRequest(), null); - - listener.close(); - })(); - - const resp = await fetch(`http://127.0.0.1:${listenPort}/`, { - body: stream.readable, - method: "POST", - headers: { "connection": "close" }, - }); - - await resp.arrayBuffer(); - await promise; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerStreamDuplex() { - let httpConn: Deno.HttpConn; - const listener = Deno.listen({ port: listenPort }); - const promise = (async () => { - const conn = await listener.accept(); - httpConn = Deno.serveHttp(conn); - const evt = await httpConn.nextRequest(); - assert(evt); - const { request, respondWith } = evt; - assert(request.body); - await respondWith(new Response(request.body)); - })(); - - const ts = new TransformStream(); - const writable = ts.writable.getWriter(); - const resp = await fetch(`http://127.0.0.1:${listenPort}/`, { - method: "POST", - body: ts.readable, - }); - assert(resp.body); - const reader = resp.body.getReader(); - await writable.write(new Uint8Array([1])); - const chunk1 = await reader.read(); - assert(!chunk1.done); - assertEquals(chunk1.value, new Uint8Array([1])); - await writable.write(new Uint8Array([2])); - const chunk2 = await reader.read(); - assert(!chunk2.done); - assertEquals(chunk2.value, new Uint8Array([2])); - - await writable.close(); - const chunk3 = await reader.read(); - assert(chunk3.done); - await promise; - httpConn!.close(); - listener.close(); - }, -); - -Deno.test({ permissions: { net: true } }, async function httpServerClose() { - const listener = Deno.listen({ port: listenPort }); - const client = await Deno.connect({ port: listenPort }); - const httpConn = Deno.serveHttp(await listener.accept()); - client.close(); - const evt = await httpConn.nextRequest(); - assertEquals(evt, null); - // Note httpConn is automatically closed when "done" is reached. - listener.close(); -}); - -Deno.test( - { permissions: { net: true } }, - async function httpServerInvalidMethod() { - const listener = Deno.listen({ port: listenPort }); - const client = await Deno.connect({ port: listenPort }); - const httpConn = Deno.serveHttp(await listener.accept()); - await client.write(new Uint8Array([1, 2, 3])); - await assertRejects( - async () => { - await httpConn.nextRequest(); - }, - Deno.errors.Http, - "invalid HTTP method parsed", - ); - // Note httpConn is automatically closed when it errors. - client.close(); - listener.close(); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function httpServerWithTls() { - const hostname = "localhost"; - const port = listenPort; - - const promise = (async () => { - const listener = Deno.listenTls({ - hostname, - port, - cert: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.crt"), - key: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.key"), - }); - const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); - const evt = await httpConn.nextRequest(); - assert(evt); - const { respondWith } = evt; - await respondWith(new Response("Hello World")); - - // TODO(ry) If we don't call httpConn.nextRequest() here we get "error sending - // request for url (https://localhost:${listenPort}/): connection closed before - // message completed". - assertEquals(await httpConn.nextRequest(), null); - - listener.close(); - })(); - - const caCert = Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem"); - const client = Deno.createHttpClient({ caCerts: [caCert] }); - const resp = await fetch(`https://${hostname}:${port}/`, { - headers: { "connection": "close" }, - client, - }); - client.close(); - const respBody = await resp.text(); - assertEquals("Hello World", respBody); - await promise; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerRegressionHang() { - let httpConn: Deno.HttpConn; - const listener = Deno.listen({ port: listenPort }); - const promise = (async () => { - const conn = await listener.accept(); - httpConn = Deno.serveHttp(conn); - const event = await httpConn.nextRequest(); - assert(event); - const { request, respondWith } = event; - const reqBody = await request.text(); - assertEquals("request", reqBody); - await respondWith(new Response("response")); - })(); - - const resp = await fetch(`http://127.0.0.1:${listenPort}/`, { - method: "POST", - body: "request", - }); - const respBody = await resp.text(); - assertEquals("response", respBody); - await promise; - - httpConn!.close(); - listener.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerCancelBodyOnResponseFailure() { - const promise = (async () => { - const listener = Deno.listen({ port: listenPort }); - const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); - const event = await httpConn.nextRequest(); - assert(event); - const { respondWith } = event; - let cancelReason: string; - await assertRejects( - async () => { - let interval = 0; - await respondWith( - new Response( - new ReadableStream({ - start(controller) { - interval = setInterval(() => { - const message = `data: ${Date.now()}\n\n`; - controller.enqueue(new TextEncoder().encode(message)); - }, 200); - }, - cancel(reason) { - cancelReason = reason; - clearInterval(interval); - }, - }), - ), - ); - }, - Deno.errors.Http, - cancelReason!, - ); - assert(cancelReason!); - httpConn!.close(); - listener.close(); - })(); - - const resp = await fetch(`http://127.0.0.1:${listenPort}/`); - await resp.body!.cancel(); - await promise; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerNextRequestErrorExposedInResponse() { - const promise = (async () => { - const listener = Deno.listen({ port: listenPort }); - const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); - const event = await httpConn.nextRequest(); - assert(event); - // Start polling for the next request before awaiting response. - const nextRequestPromise = httpConn.nextRequest(); - const { respondWith } = event; - await assertRejects( - async () => { - let interval = 0; - await respondWith( - new Response( - new ReadableStream({ - start(controller) { - interval = setInterval(() => { - const message = `data: ${Date.now()}\n\n`; - controller.enqueue(new TextEncoder().encode(message)); - }, 200); - }, - cancel() { - clearInterval(interval); - }, - }), - ), - ); - }, - Deno.errors.Http, - "connection closed", - ); - // The error from `op_http_accept` reroutes to `respondWith()`. - assertEquals(await nextRequestPromise, null); - listener.close(); - })(); - - const resp = await fetch(`http://127.0.0.1:${listenPort}/`); - await resp.body!.cancel(); - await promise; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerEmptyBlobResponse() { - let httpConn: Deno.HttpConn; - const listener = Deno.listen({ port: listenPort }); - const promise = (async () => { - const conn = await listener.accept(); - httpConn = Deno.serveHttp(conn); - const event = await httpConn.nextRequest(); - assert(event); - const { respondWith } = event; - await respondWith(new Response(new Blob([]))); - })(); - - const resp = await fetch(`http://127.0.0.1:${listenPort}/`); - const respBody = await resp.text(); - assertEquals("", respBody); - await promise; - httpConn!.close(); - listener.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerNextRequestResolvesOnClose() { - const httpConnList: Deno.HttpConn[] = []; - - async function serve(l: Deno.Listener) { - for await (const conn of l) { - (async () => { - const c = Deno.serveHttp(conn); - httpConnList.push(c); - for await (const { respondWith } of c) { - respondWith(new Response("hello")); - } - })(); - } - } - - const l = Deno.listen({ port: listenPort }); - serve(l); - - await delay(300); - const res = await fetch(`http://localhost:${listenPort}/`); - const _text = await res.text(); - - // Close connection and listener. - httpConnList.forEach((conn) => conn.close()); - l.close(); - - await delay(300); - }, -); - -Deno.test( - { permissions: { net: true } }, - // Issue: https://github.com/denoland/deno/issues/10870 - async function httpServerHang() { - // Quick and dirty way to make a readable stream from a string. Alternatively, - // `readableStreamFromReader(file)` could be used. - function stream(s: string): ReadableStream<Uint8Array> { - return new Response(s).body!; - } - - const httpConns: Deno.HttpConn[] = []; - const promise = (async () => { - let count = 0; - const listener = Deno.listen({ port: listenPort }); - for await (const conn of listener) { - (async () => { - const httpConn = Deno.serveHttp(conn); - httpConns.push(httpConn); - for await (const { respondWith } of httpConn) { - respondWith(new Response(stream("hello"))); - - count++; - if (count >= 2) { - listener.close(); - } - } - })(); - } - })(); - - const clientConn = await Deno.connect({ port: listenPort }); - - const r1 = await writeRequestAndReadResponse(clientConn); - assertEquals(r1, "hello"); - - const r2 = await writeRequestAndReadResponse(clientConn); - assertEquals(r2, "hello"); - - clientConn.close(); - await promise; - for (const conn of httpConns) { - conn.close(); - } - }, -); - -Deno.test( - { permissions: { net: true } }, - // Issue: https://github.com/denoland/deno/issues/10930 - async function httpServerStreamingResponse() { - // This test enqueues a single chunk for readable - // stream and waits for client to read that chunk and signal - // it before enqueueing subsequent chunk. Issue linked above - // presented a situation where enqueued chunks were not - // written to the HTTP connection until the next chunk was enqueued. - - let counter = 0; - - const deferreds = [ - Promise.withResolvers<void>(), - Promise.withResolvers<void>(), - Promise.withResolvers<void>(), - ]; - - async function writeRequest(conn: Deno.Conn) { - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - const w = new BufWriter(conn); - const r = new BufReader(conn); - const body = `GET / HTTP/1.1\r\nHost: 127.0.0.1:${listenPort}\r\n\r\n`; - const writeResult = await w.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await w.flush(); - const tpr = new TextProtoReader(r); - const statusLine = await tpr.readLine(); - assert(statusLine !== null); - const headers = await tpr.readMimeHeader(); - assert(headers !== null); - - const chunkedReader = chunkedBodyReader(headers, r); - const buf = new Uint8Array(5); - const dest = new Buffer(); - let result: number | null; - while ((result = await chunkedReader.read(buf)) !== null) { - const len = Math.min(buf.byteLength, result); - await dest.write(buf.subarray(0, len)); - // Resolve a deferred - this will make response stream to - // enqueue next chunk. - deferreds[counter - 1].resolve(); - } - return decoder.decode(dest.bytes()); - } - - function periodicStream() { - return new ReadableStream({ - start(controller) { - controller.enqueue(`${counter}\n`); - counter++; - }, - - async pull(controller) { - if (counter >= 3) { - return controller.close(); - } - - await deferreds[counter - 1].promise; - - controller.enqueue(`${counter}\n`); - counter++; - }, - }).pipeThrough(new TextEncoderStream()); - } - - let httpConn: Deno.HttpConn; - const listener = Deno.listen({ port: listenPort }); - const finished = (async () => { - const conn = await listener.accept(); - httpConn = Deno.serveHttp(conn); - const requestEvent = await httpConn.nextRequest(); - const { respondWith } = requestEvent!; - await respondWith(new Response(periodicStream())); - })(); - - // start a client - const clientConn = await Deno.connect({ port: listenPort }); - - const r1 = await writeRequest(clientConn); - assertEquals(r1, "0\n1\n2\n"); - - await finished; - clientConn.close(); - - httpConn!.close(); - listener.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpRequestLatin1Headers() { - let httpConn: Deno.HttpConn; - const promise = (async () => { - const listener = Deno.listen({ port: listenPort }); - const conn = await listener.accept(); - listener.close(); - httpConn = Deno.serveHttp(conn); - const reqEvent = await httpConn.nextRequest(); - assert(reqEvent); - const { request, respondWith } = reqEvent; - assertEquals(request.headers.get("X-Header-Test"), "á"); - await respondWith( - new Response("", { headers: { "X-Header-Test": "Æ" } }), - ); - })(); - - const clientConn = await Deno.connect({ port: listenPort }); - const requestText = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:${listenPort}\r\nX-Header-Test: á\r\n\r\n`; - const requestBytes = new Uint8Array(requestText.length); - for (let i = 0; i < requestText.length; i++) { - requestBytes[i] = requestText.charCodeAt(i); - } - let written = 0; - while (written < requestBytes.byteLength) { - written += await clientConn.write(requestBytes.slice(written)); - } - - let responseText = ""; - const buf = new Uint8Array(1024); - let read; - - while ((read = await clientConn.read(buf)) !== null) { - httpConn!.close(); - for (let i = 0; i < read; i++) { - responseText += String.fromCharCode(buf[i]); - } - } - - clientConn.close(); - - assert(/\r\n[Xx]-[Hh]eader-[Tt]est: Æ\r\n/.test(responseText)); - - await promise; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerRequestWithoutPath() { - let httpConn: Deno.HttpConn; - const listener = Deno.listen({ port: listenPort }); - const promise = (async () => { - const conn = await listener.accept(); - listener.close(); - httpConn = Deno.serveHttp(conn); - const reqEvent = await httpConn.nextRequest(); - assert(reqEvent); - const { request, respondWith } = reqEvent; - assertEquals( - new URL(request.url).href, - `http://127.0.0.1:${listenPort}/`, - ); - assertEquals(await request.text(), ""); - await respondWith(new Response()); - })(); - - const clientConn = await Deno.connect({ port: listenPort }); - - async function writeRequest(conn: Deno.Conn) { - const encoder = new TextEncoder(); - - const w = new BufWriter(conn); - const r = new BufReader(conn); - const body = - `CONNECT 127.0.0.1:${listenPort} HTTP/1.1\r\nHost: 127.0.0.1:${listenPort}\r\n\r\n`; - const writeResult = await w.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await w.flush(); - const tpr = new TextProtoReader(r); - const statusLine = await tpr.readLine(); - assert(statusLine !== null); - const m = statusLine.match(/^(.+?) (.+?) (.+?)$/); - assert(m !== null, "must be matched"); - const [_, _proto, status, _ok] = m; - assertEquals(status, "200"); - const headers = await tpr.readMimeHeader(); - assert(headers !== null); - } - - await writeRequest(clientConn); - clientConn.close(); - await promise; - httpConn!.close(); - }, -); - -Deno.test({ permissions: { net: true } }, async function httpServerWebSocket() { - const promise = (async () => { - const listener = Deno.listen({ port: listenPort }); - const conn = await listener.accept(); - listener.close(); - const httpConn = Deno.serveHttp(conn); - const reqEvent = await httpConn.nextRequest(); - assert(reqEvent); - const { request, respondWith } = reqEvent; - const { - response, - socket, - } = Deno.upgradeWebSocket(request); - socket.onerror = () => fail(); - socket.onmessage = (m) => { - socket.send(m.data); - socket.close(1001); - }; - const close = new Promise<void>((resolve) => { - socket.onclose = () => resolve(); - }); - await respondWith(response); - await close; - })(); - - const def = Promise.withResolvers<void>(); - const ws = new WebSocket(`ws://localhost:${listenPort}`); - ws.onmessage = (m) => assertEquals(m.data, "foo"); - ws.onerror = () => fail(); - ws.onclose = () => def.resolve(); - ws.onopen = () => ws.send("foo"); - await def.promise; - await promise; -}); - -Deno.test(function httpUpgradeWebSocket() { - const request = new Request("https://deno.land/", { - headers: { - connection: "Upgrade", - upgrade: "websocket", - "sec-websocket-key": "dGhlIHNhbXBsZSBub25jZQ==", - }, - }); - const { response } = Deno.upgradeWebSocket(request); - assertEquals(response.status, 101); - assertEquals(response.headers.get("connection"), "Upgrade"); - assertEquals(response.headers.get("upgrade"), "websocket"); - assertEquals( - response.headers.get("sec-websocket-accept"), - "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", - ); -}); - -Deno.test(function httpUpgradeWebSocketMultipleConnectionOptions() { - const request = new Request("https://deno.land/", { - headers: { - connection: "keep-alive, upgrade", - upgrade: "websocket", - "sec-websocket-key": "dGhlIHNhbXBsZSBub25jZQ==", - }, - }); - const { response } = Deno.upgradeWebSocket(request); - assertEquals(response.status, 101); -}); - -Deno.test(function httpUpgradeWebSocketMultipleUpgradeOptions() { - const request = new Request("https://deno.land/", { - headers: { - connection: "upgrade", - upgrade: "websocket, foo", - "sec-websocket-key": "dGhlIHNhbXBsZSBub25jZQ==", - }, - }); - const { response } = Deno.upgradeWebSocket(request); - assertEquals(response.status, 101); -}); - -Deno.test(function httpUpgradeWebSocketCaseInsensitiveUpgradeHeader() { - const request = new Request("https://deno.land/", { - headers: { - connection: "upgrade", - upgrade: "Websocket", - "sec-websocket-key": "dGhlIHNhbXBsZSBub25jZQ==", - }, - }); - const { response } = Deno.upgradeWebSocket(request); - assertEquals(response.status, 101); -}); - -Deno.test(function httpUpgradeWebSocketInvalidUpgradeHeader() { - assertThrows( - () => { - const request = new Request("https://deno.land/", { - headers: { - connection: "upgrade", - upgrade: "invalid", - "sec-websocket-key": "dGhlIHNhbXBsZSBub25jZQ==", - }, - }); - Deno.upgradeWebSocket(request); - }, - TypeError, - "Invalid Header: 'upgrade' header must contain 'websocket'", - ); -}); - -Deno.test(function httpUpgradeWebSocketWithoutUpgradeHeader() { - assertThrows( - () => { - const request = new Request("https://deno.land/", { - headers: { - connection: "upgrade", - "sec-websocket-key": "dGhlIHNhbXBsZSBub25jZQ==", - }, - }); - Deno.upgradeWebSocket(request); - }, - TypeError, - "Invalid Header: 'upgrade' header must contain 'websocket'", - ); -}); - -Deno.test( - { permissions: { net: true } }, - async function httpCookieConcatenation() { - let httpConn: Deno.HttpConn; - const promise = (async () => { - const listener = Deno.listen({ port: listenPort }); - const conn = await listener.accept(); - listener.close(); - httpConn = Deno.serveHttp(conn); - const reqEvent = await httpConn.nextRequest(); - assert(reqEvent); - const { request, respondWith } = reqEvent; - assertEquals( - new URL(request.url).href, - `http://127.0.0.1:${listenPort}/`, - ); - assertEquals(await request.text(), ""); - assertEquals(request.headers.get("cookie"), "foo=bar; bar=foo"); - await respondWith(new Response("ok")); - })(); - - const resp = await fetch(`http://127.0.0.1:${listenPort}/`, { - headers: [ - ["connection", "close"], - ["cookie", "foo=bar"], - ["cookie", "bar=foo"], - ], - }); - const text = await resp.text(); - assertEquals(text, "ok"); - await promise; - httpConn!.close(); - }, -); - -// https://github.com/denoland/deno/issues/11651 -Deno.test({ permissions: { net: true } }, async function httpServerPanic() { - const listener = Deno.listen({ port: listenPort }); - const client = await Deno.connect({ port: listenPort }); - const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); - - // This message is incomplete on purpose, we'll forcefully close client connection - // after it's flushed to cause connection to error out on the server side. - const encoder = new TextEncoder(); - await client.write(encoder.encode("GET / HTTP/1.1")); - - httpConn.nextRequest(); - await client.write(encoder.encode("\r\n\r\n")); - httpConn!.close(); - - client.close(); - listener.close(); -}); - -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerCorrectSizeResponse() { - const tmpFile = await Deno.makeTempFile(); - using file = await Deno.open(tmpFile, { write: true, read: true }); - await file.write(new Uint8Array(70 * 1024).fill(1)); // 70kb sent in 64kb + 6kb chunks - - let httpConn: Deno.HttpConn; - const listener = Deno.listen({ port: listenPort }); - const promise = (async () => { - const conn = await listener.accept(); - httpConn = Deno.serveHttp(conn); - const ev = await httpConn.nextRequest(); - const { respondWith } = ev!; - const f = await Deno.open(tmpFile, { read: true }); - await respondWith(new Response(f.readable, { status: 200 })); - })(); - const resp = await fetch(`http://127.0.0.1:${listenPort}/`); - const body = await resp.arrayBuffer(); - assertEquals(body.byteLength, 70 * 1024); - await promise; - httpConn!.close(); - listener.close(); - }, -); - -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerClosedStream() { - const listener = Deno.listen({ port: listenPort }); - - const client = await Deno.connect({ port: listenPort }); - await client.write(new TextEncoder().encode( - `GET / HTTP/1.0\r\n\r\n`, - )); - - const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); - const ev = await httpConn.nextRequest(); - const { respondWith } = ev!; - - const tmpFile = await Deno.makeTempFile(); - const file = await Deno.open(tmpFile, { write: true, read: true }); - await file.write(new TextEncoder().encode("hello")); - - const reader = await file.readable.getReader(); - while (true) { - const { done, value } = await reader.read(); - if (done) break; - assert(value); - } - - let didThrow = false; - try { - await respondWith(new Response(file.readable)); - } catch { - // pass - didThrow = true; - } - - assert(didThrow); - httpConn!.close(); - listener.close(); - client.close(); - }, -); - -// https://github.com/denoland/deno/issues/11595 -Deno.test( - { permissions: { net: true } }, - async function httpServerIncompleteMessage() { - const listener = Deno.listen({ port: listenPort }); - - const client = await Deno.connect({ port: listenPort }); - await client.write(new TextEncoder().encode( - `GET / HTTP/1.0\r\n\r\n`, - )); - - const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); - const ev = await httpConn.nextRequest(); - const { respondWith } = ev!; - - const errors: Error[] = []; - - const readable = new ReadableStream({ - async pull(controller) { - client.close(); - await delay(1000); - controller.enqueue(new TextEncoder().encode( - "written to the writable side of a TransformStream", - )); - controller.close(); - }, - cancel(error) { - errors.push(error); - }, - }); - - const res = new Response(readable); - - await respondWith(res).catch((error: Error) => errors.push(error)); - - httpConn!.close(); - listener.close(); - - assert(errors.length >= 1); - for (const error of errors) { - assertEquals(error.name, "Http"); - assert(error.message.includes("connection")); - } - }, -); - -// https://github.com/denoland/deno/issues/11743 -Deno.test( - { permissions: { net: true } }, - async function httpServerDoesntLeakResources() { - const listener = Deno.listen({ port: listenPort }); - const [conn, clientConn] = await Promise.all([ - listener.accept(), - Deno.connect({ port: listenPort }), - ]); - const httpConn = Deno.serveHttp(conn); - - await Promise.all([ - httpConn.nextRequest(), - clientConn.write(new TextEncoder().encode( - `GET / HTTP/1.1\r\nHost: 127.0.0.1:${listenPort}\r\n\r\n`, - )), - ]); - - httpConn!.close(); - listener.close(); - clientConn.close(); - }, -); - -// https://github.com/denoland/deno/issues/11926 -// verify that the only new resource is "httpConnection", to make -// sure "request" resource is closed even if its body was not read -// by server handler -Deno.test( - { permissions: { net: true } }, - async function httpServerDoesntLeakResources2() { - let listener: Deno.Listener; - let httpConn: Deno.HttpConn; - - const promise = (async () => { - listener = Deno.listen({ port: listenPort }); - for await (const conn of listener) { - httpConn = Deno.serveHttp(conn); - for await (const { request, respondWith } of httpConn) { - assertEquals( - new URL(request.url).href, - `http://127.0.0.1:${listenPort}/`, - ); - // not reading request body on purpose - respondWith(new Response("ok")); - } - } - })(); - - const response = await fetch(`http://127.0.0.1:${listenPort}`, { - method: "POST", - body: "hello world", - }); - await response.text(); - - listener!.close(); - httpConn!.close(); - await promise; - }, -); - -// https://github.com/denoland/deno/pull/12216 -Deno.test( - { permissions: { net: true } }, - async function droppedConnSenderNoPanic() { - async function server() { - const listener = Deno.listen({ port: listenPort }); - const conn = await listener.accept(); - const http = Deno.serveHttp(conn); - const evt = await http.nextRequest(); - http.close(); - try { - await evt!.respondWith(new Response("boom")); - } catch { - // Ignore error. - } - listener.close(); - } - - async function client() { - try { - const resp = await fetch(`http://127.0.0.1:${listenPort}/`); - await resp.body?.cancel(); - } catch { - // Ignore error - } - } - - await Promise.all([server(), client()]); - }, -); - -// https://github.com/denoland/deno/issues/12193 -Deno.test( - { permissions: { net: true } }, - async function httpConnConcurrentNextRequestCalls() { - const hostname = "localhost"; - const port = listenPort; - - let httpConn: Deno.HttpConn; - const listener = Deno.listen({ hostname, port }); - async function server() { - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const promises = new Array(10).fill(null).map(async (_, i) => { - const event = await httpConn.nextRequest(); - assert(event); - const { pathname } = new URL(event.request.url); - assertStrictEquals(pathname, `/${i}`); - const response = new Response(`Response #${i}`); - await event.respondWith(response); - }); - await Promise.all(promises); - } - - async function client() { - for (let i = 0; i < 10; i++) { - const response = await fetch(`http://${hostname}:${port}/${i}`); - const body = await response.text(); - assertStrictEquals(body, `Response #${i}`); - } - } - - await Promise.all([server(), delay(100).then(client)]); - httpConn!.close(); - listener.close(); - }, -); - -// https://github.com/denoland/deno/pull/12704 -// https://github.com/denoland/deno/pull/12732 -Deno.test( - { permissions: { net: true } }, - async function httpConnAutoCloseDelayedOnUpgrade() { - const hostname = "localhost"; - const port = listenPort; - - async function server() { - const listener = Deno.listen({ hostname, port }); - const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); - - const event1 = await httpConn.nextRequest() as Deno.RequestEvent; - const event2Promise = httpConn.nextRequest(); - - const { socket, response } = Deno.upgradeWebSocket(event1.request); - socket.onmessage = (event) => socket.send(event.data); - const socketClosed = new Promise<void>((resolve) => { - socket.onclose = () => resolve(); - }); - event1.respondWith(response); - - const event2 = await event2Promise; - assertStrictEquals(event2, null); - - listener.close(); - await socketClosed; - } - - async function client() { - const socket = new WebSocket(`ws://${hostname}:${port}/`); - socket.onopen = () => socket.send("bla bla"); - const closed = new Promise<void>((resolve) => { - socket.onclose = () => resolve(); - }); - const { data } = await new Promise<MessageEvent<string>>((res) => - socket.onmessage = res - ); - assertStrictEquals(data, "bla bla"); - socket.close(); - await closed; - } - - await Promise.all([server(), client()]); - }, -); - -// https://github.com/denoland/deno/issues/12741 -// https://github.com/denoland/deno/pull/12746 -// https://github.com/denoland/deno/pull/12798 -Deno.test( - { permissions: { net: true, run: true } }, - async function httpServerDeleteRequestHasBody() { - const hostname = "localhost"; - const port = listenPort; - - let httpConn: Deno.HttpConn; - const listener = Deno.listen({ hostname, port }); - async function server() { - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const event = await httpConn.nextRequest() as Deno.RequestEvent; - assert(event.request.body); - const response = new Response(); - await event.respondWith(response); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = ["-X", "DELETE", url]; - const { success } = await new Deno.Command("curl", { - args, - stdout: "null", - stderr: "null", - }).output(); - assert(success); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - listener.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerRespondNonAsciiUint8Array() { - let httpConn: Deno.HttpConn; - const listener = Deno.listen({ port: listenPort }); - const promise = (async () => { - const conn = await listener.accept(); - listener.close(); - httpConn = Deno.serveHttp(conn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.body, null); - await respondWith( - new Response(new Uint8Array([128]), {}), - ); - })(); - - const resp = await fetch(`http://localhost:${listenPort}/`); - assertEquals(resp.status, 200); - const body = await resp.arrayBuffer(); - assertEquals(new Uint8Array(body), new Uint8Array([128])); - - await promise; - httpConn!.close(); - }, -); - -function tmpUnixSocketPath(): string { - const folder = Deno.makeTempDirSync(); - return join(folder, "socket"); -} - -// https://github.com/denoland/deno/pull/13628 -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - async function httpServerOnUnixSocket() { - const filePath = tmpUnixSocketPath(); - - let httpConn: Deno.HttpConn; - const promise = (async () => { - const listener = Deno.listen({ path: filePath, transport: "unix" }); - const conn = await listener.accept(); - listener.close(); - httpConn = Deno.serveHttp(conn); - const reqEvent = await httpConn.nextRequest(); - assert(reqEvent); - const { request, respondWith } = reqEvent; - const url = new URL(request.url); - assertEquals(url.protocol, "http+unix:"); - assertEquals(decodeURIComponent(url.host), filePath); - assertEquals(url.pathname, "/path/name"); - await respondWith(new Response("", { headers: {} })); - })(); - - // fetch() does not supports unix domain sockets yet https://github.com/denoland/deno/issues/8821 - const conn = await Deno.connect({ path: filePath, transport: "unix" }); - const encoder = new TextEncoder(); - // The Host header must be present and empty if it is not a Internet host name (RFC2616, Section 14.23) - const body = `GET /path/name HTTP/1.1\r\nHost:\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - const resp = new Uint8Array(200); - const readResult = await conn.read(resp); - assertEquals(readResult, 138); - - conn.close(); - - await promise; - httpConn!.close(); - }, -); - -/* Automatic Body Compression */ - -const decoder = new TextDecoder(); - -Deno.test({ - name: "http server compresses body - check headers", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - const listener = Deno.listen({ hostname, port }); - - const data = { hello: "deno", now: "with", compressed: "body" }; - - let httpConn: Deno.HttpConn; - async function server() { - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br"); - const response = new Response(JSON.stringify(data), { - headers: { "content-type": "application/json" }, - }); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = [ - "-i", - "--request", - "GET", - "--url", - url, - "--header", - "Accept-Encoding: gzip, deflate, br", - ]; - const { success, stdout } = await new Deno.Command("curl", { - args, - stderr: "null", - stdout: "piped", - }).output(); - assert(success); - const output = decoder.decode(stdout); - assert(output.includes("vary: Accept-Encoding\r\n")); - assert(output.includes("content-encoding: gzip\r\n")); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test({ - name: "http server compresses body - check body", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - const listener = Deno.listen({ hostname, port }); - - const data = { hello: "deno", now: "with", compressed: "body" }; - - let httpConn: Deno.HttpConn; - async function server() { - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br"); - const response = new Response(JSON.stringify(data), { - headers: { "content-type": "application/json" }, - }); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = [ - "--request", - "GET", - "--url", - url, - "--header", - "Accept-Encoding: gzip, deflate, br", - ]; - const proc = new Deno.Command("curl", { - args, - stderr: "null", - stdout: "piped", - }).spawn(); - const status = await proc.status; - assert(status.success); - const stdout = proc.stdout - .pipeThrough(new DecompressionStream("gzip")) - .pipeThrough(new TextDecoderStream()); - let body = ""; - for await (const chunk of stdout) { - body += chunk; - } - assertEquals(JSON.parse(body), data); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test({ - name: "http server doesn't compress small body", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - - let httpConn: Deno.HttpConn; - async function server() { - const listener = Deno.listen({ hostname, port }); - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br"); - const response = new Response( - JSON.stringify({ hello: "deno" }), - { - headers: { "content-type": "application/json" }, - }, - ); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = [ - "-i", - "--request", - "GET", - "--url", - url, - "--header", - "Accept-Encoding: gzip, deflate, br", - ]; - const { success, stdout } = await new Deno.Command("curl", { - args, - stderr: "null", - stdout: "piped", - }).output(); - assert(success); - const output = decoder.decode(stdout).toLocaleLowerCase(); - assert(output.includes("vary: accept-encoding\r\n")); - assert(!output.includes("content-encoding: ")); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test({ - name: "http server respects accept-encoding weights", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - - let httpConn: Deno.HttpConn; - async function server() { - const listener = Deno.listen({ hostname, port }); - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals( - request.headers.get("Accept-Encoding"), - "gzip;q=0.8, br;q=1.0, *;q=0.1", - ); - const response = new Response( - JSON.stringify({ hello: "deno", now: "with", compressed: "body" }), - { - headers: { "content-type": "application/json" }, - }, - ); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = [ - "-i", - "--request", - "GET", - "--url", - url, - "--header", - "Accept-Encoding: gzip;q=0.8, br;q=1.0, *;q=0.1", - ]; - const { success, stdout } = await new Deno.Command("curl", { - args, - stderr: "null", - stdout: "piped", - }).output(); - assert(success); - const output = decoder.decode(stdout); - assert(output.includes("vary: Accept-Encoding\r\n")); - assert(output.includes("content-encoding: br\r\n")); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test({ - name: "http server augments vary header", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - - let httpConn: Deno.HttpConn; - async function server() { - const listener = Deno.listen({ hostname, port }); - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br"); - const response = new Response( - JSON.stringify({ hello: "deno", now: "with", compressed: "body" }), - { - headers: { "content-type": "application/json", vary: "Accept" }, - }, - ); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = [ - "-i", - "--request", - "GET", - "--url", - url, - "--header", - "Accept-Encoding: gzip, deflate, br", - ]; - const { success, stdout } = await new Deno.Command("curl", { - args, - stderr: "null", - stdout: "piped", - }).output(); - assert(success); - const output = decoder.decode(stdout); - assert(output.includes("vary: Accept-Encoding, Accept\r\n")); - assert(output.includes("content-encoding: gzip\r\n")); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test({ - name: "http server weakens etag header", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - - let httpConn: Deno.HttpConn; - async function server() { - const listener = Deno.listen({ hostname, port }); - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br"); - const response = new Response( - JSON.stringify({ hello: "deno", now: "with", compressed: "body" }), - { - headers: { - "content-type": "application/json", - etag: "33a64df551425fcc55e4d42a148795d9f25f89d4", - }, - }, - ); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = [ - "curl", - "-i", - "--request", - "GET", - "--url", - url, - "--header", - "Accept-Encoding: gzip, deflate, br", - ]; - const { success, stdout } = await new Deno.Command("curl", { - args, - stderr: "null", - stdout: "piped", - }).output(); - assert(success); - const output = decoder.decode(stdout); - assert(output.includes("vary: Accept-Encoding\r\n")); - assert( - output.includes("etag: W/33a64df551425fcc55e4d42a148795d9f25f89d4\r\n"), - ); - assert(output.includes("content-encoding: gzip\r\n")); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test({ - name: "http server passes through weak etag header", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - - let httpConn: Deno.HttpConn; - async function server() { - const listener = Deno.listen({ hostname, port }); - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br"); - const response = new Response( - JSON.stringify({ hello: "deno", now: "with", compressed: "body" }), - { - headers: { - "content-type": "application/json", - etag: "W/33a64df551425fcc55e4d42a148795d9f25f89d4", - }, - }, - ); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = [ - "-i", - "--request", - "GET", - "--url", - url, - "--header", - "Accept-Encoding: gzip, deflate, br", - ]; - const { success, stdout } = await new Deno.Command("curl", { - args, - stderr: "null", - stdout: "piped", - }).output(); - assert(success); - const output = decoder.decode(stdout); - assert(output.includes("vary: Accept-Encoding\r\n")); - assert( - output.includes("etag: W/33a64df551425fcc55e4d42a148795d9f25f89d4\r\n"), - ); - assert(output.includes("content-encoding: gzip\r\n")); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test({ - name: "http server doesn't compress body when no-transform is set", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - - let httpConn: Deno.HttpConn; - async function server() { - const listener = Deno.listen({ hostname, port }); - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br"); - const response = new Response( - JSON.stringify({ hello: "deno", now: "with", compressed: "body" }), - { - headers: { - "content-type": "application/json", - "cache-control": "no-transform", - }, - }, - ); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = [ - "-i", - "--request", - "GET", - "--url", - url, - "--header", - "Accept-Encoding: gzip, deflate, br", - ]; - const { success, stdout } = await new Deno.Command("curl", { - args, - stderr: "null", - stdout: "piped", - }).output(); - assert(success); - const output = decoder.decode(stdout); - assert(output.includes("vary: Accept-Encoding\r\n")); - assert(!output.includes("content-encoding: ")); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test({ - name: "http server doesn't compress body when content-range is set", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - - let httpConn: Deno.HttpConn; - async function server() { - const listener = Deno.listen({ hostname, port }); - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br"); - const response = new Response( - JSON.stringify({ hello: "deno", now: "with", compressed: "body" }), - { - headers: { - "content-type": "application/json", - "content-range": "bytes 200-100/67589", - }, - }, - ); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = [ - "-i", - "--request", - "GET", - "--url", - url, - "--header", - "Accept-Encoding: gzip, deflate, br", - ]; - const { success, stdout } = await new Deno.Command("curl", { - args, - stderr: "null", - stdout: "piped", - }).output(); - assert(success); - const output = decoder.decode(stdout); - assert(output.includes("vary: Accept-Encoding\r\n")); - assert(!output.includes("content-encoding: ")); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test({ - name: "http server compresses streamed bodies - check headers", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - - const encoder = new TextEncoder(); - const listener = Deno.listen({ hostname, port }); - - const data = { hello: "deno", now: "with", compressed: "body" }; - - let httpConn: Deno.HttpConn; - async function server() { - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br"); - const bodyInit = new ReadableStream({ - start(controller) { - controller.enqueue(encoder.encode(JSON.stringify(data))); - controller.close(); - }, - }); - const response = new Response( - bodyInit, - { headers: { "content-type": "application/json" } }, - ); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = [ - "curl", - "-i", - "--request", - "GET", - "--url", - url, - "--header", - "Accept-Encoding: gzip, deflate, br", - ]; - const { success, stdout } = await new Deno.Command("curl", { - args, - stderr: "null", - stdout: "piped", - }).output(); - assert(success); - const output = decoder.decode(stdout); - assert(output.includes("vary: Accept-Encoding\r\n")); - assert(output.includes("content-encoding: gzip\r\n")); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test({ - name: "http server compresses streamed bodies - check body", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - - const encoder = new TextEncoder(); - const listener = Deno.listen({ hostname, port }); - - const data = { hello: "deno", now: "with", compressed: "body" }; - - let httpConn: Deno.HttpConn; - async function server() { - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br"); - const bodyInit = new ReadableStream({ - start(controller) { - controller.enqueue(encoder.encode(JSON.stringify(data))); - controller.close(); - }, - }); - const response = new Response( - bodyInit, - { headers: { "content-type": "application/json" } }, - ); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = [ - "--request", - "GET", - "--url", - url, - "--header", - "Accept-Encoding: gzip, deflate, br", - ]; - const proc = new Deno.Command("curl", { - args, - stderr: "null", - stdout: "piped", - }).spawn(); - const status = await proc.status; - assert(status.success); - const stdout = proc.stdout - .pipeThrough(new DecompressionStream("gzip")) - .pipeThrough(new TextDecoderStream()); - let body = ""; - for await (const chunk of stdout) { - body += chunk; - } - assertEquals(JSON.parse(body), data); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test({ - name: "http server updates content-length header if compression is applied", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - let contentLength: string; - - let httpConn: Deno.HttpConn; - async function server() { - const listener = Deno.listen({ hostname, port }); - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br"); - const body = JSON.stringify({ - hello: "deno", - now: "with", - compressed: "body", - }); - contentLength = String(body.length); - const response = new Response( - body, - { - headers: { - "content-type": "application/json", - "content-length": contentLength, - }, - }, - ); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = [ - "-i", - "--request", - "GET", - "--url", - url, - "--header", - "Accept-Encoding: gzip, deflate, br", - ]; - const { success, stdout } = await new Deno.Command("curl", { - args, - stderr: "null", - stdout: "piped", - }).output(); - assert(success); - const output = decoder.decode(stdout); - assert(output.includes("vary: Accept-Encoding\r\n")); - assert(output.includes("content-encoding: gzip\r\n")); - // Ensure the content-length header is updated (but don't check the exact length). - assert(!output.includes(`content-length: ${contentLength}\r\n`)); - assert(output.includes("content-length: ")); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test({ - name: "http server compresses when accept-encoding is deflate, gzip", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - let contentLength: string; - - let httpConn: Deno.HttpConn; - async function server() { - const listener = Deno.listen({ hostname, port }); - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "deflate, gzip"); - const body = "x".repeat(10000); - contentLength = String(body.length); - const response = new Response( - body, - { - headers: { - "content-length": contentLength, - }, - }, - ); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const cmd = [ - "curl", - "-i", - "--request", - "GET", - "--url", - url, - // "--compressed", // Windows curl does not support --compressed - "--header", - "Accept-Encoding: deflate, gzip", - ]; - // deno-lint-ignore no-deprecated-deno-api - const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" }); - const status = await proc.status(); - assert(status.success); - const output = decoder.decode(await proc.output()); - assert(output.includes("vary: Accept-Encoding\r\n")); - assert(output.includes("content-encoding: gzip\r\n")); - // Ensure the content-length header is updated. - assert(!output.includes(`content-length: ${contentLength}\r\n`)); - assert(output.includes("content-length: ")); - proc.close(); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test({ - name: "http server custom content-encoding is left untouched", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - let contentLength: string; - - let httpConn: Deno.HttpConn; - async function server() { - const listener = Deno.listen({ hostname, port }); - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "deflate, gzip"); - const body = new Uint8Array([3, 1, 4, 1]); - contentLength = String(body.length); - const response = new Response( - body, - { - headers: { - "content-length": contentLength, - "content-encoding": "arbitrary", - }, - }, - ); - await respondWith(response); - listener.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const cmd = [ - "curl", - "-i", - "--request", - "GET", - "--url", - url, - // "--compressed", // Windows curl does not support --compressed - "--header", - "Accept-Encoding: deflate, gzip", - ]; - // deno-lint-ignore no-deprecated-deno-api - const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" }); - const status = await proc.status(); - assert(status.success); - const output = decoder.decode(await proc.output()); - assert(output.includes("vary: Accept-Encoding\r\n")); - assert(output.includes("content-encoding: arbitrary\r\n")); - proc.close(); - } - - await Promise.all([server(), client()]); - httpConn!.close(); - }, -}); - -Deno.test( - { permissions: { net: true } }, - async function httpServerReadLargeBodyWithContentLength() { - const TLS_PACKET_SIZE = 16 * 1024 + 256; - // We want the body to be read in multiple packets - const body = "aa\n" + "deno.land large body\n".repeat(TLS_PACKET_SIZE) + - "zz"; - - let httpConn: Deno.HttpConn; - const promise = (async () => { - const listener = Deno.listen({ port: listenPort }); - const conn = await listener.accept(); - listener.close(); - httpConn = Deno.serveHttp(conn); - const reqEvent = await httpConn.nextRequest(); - assert(reqEvent); - const { request, respondWith } = reqEvent; - assertEquals(await request.text(), body); - await respondWith(new Response(body)); - })(); - - const resp = await fetch(`http://127.0.0.1:${listenPort}/`, { - method: "POST", - headers: { "connection": "close" }, - body, - }); - const text = await resp.text(); - assertEquals(text, body); - await promise; - - httpConn!.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerReadLargeBodyWithTransferChunked() { - const TLS_PACKET_SIZE = 16 * 1024 + 256; - - // We want the body to be read in multiple packets - const chunks = [ - "aa\n", - "deno.land large body\n".repeat(TLS_PACKET_SIZE), - "zz", - ]; - - const body = chunks.join(""); - - const stream = new TransformStream(); - const writer = stream.writable.getWriter(); - for (const chunk of chunks) { - writer.write(new TextEncoder().encode(chunk)); - } - writer.close(); - - let httpConn: Deno.HttpConn; - const promise = (async () => { - const listener = Deno.listen({ port: listenPort }); - const conn = await listener.accept(); - listener.close(); - httpConn = Deno.serveHttp(conn); - const reqEvent = await httpConn.nextRequest(); - assert(reqEvent); - const { request, respondWith } = reqEvent; - assertEquals(await request.text(), body); - await respondWith(new Response(body)); - })(); - - const resp = await fetch(`http://127.0.0.1:${listenPort}/`, { - method: "POST", - headers: { "connection": "close" }, - body: stream.readable, - }); - const text = await resp.text(); - assertEquals(text, body); - await promise; - - httpConn!.close(); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function httpServerWithoutExclusiveAccessToTcp() { - const port = listenPort; - const listener = Deno.listen({ port }); - - const [clientConn, serverConn] = await Promise.all([ - Deno.connect({ port }), - listener.accept(), - ]); - - const buf = new Uint8Array(128); - const readPromise = serverConn.read(buf); - assertThrows(() => Deno.serveHttp(serverConn), Deno.errors.BadResource); - - clientConn.close(); - listener.close(); - await readPromise; - }, -); - -Deno.test( - { - permissions: { net: true, read: true }, - }, - async function httpServerWithoutExclusiveAccessToTls() { - const hostname = "localhost"; - const port = listenPort; - const listener = Deno.listenTls({ - hostname, - port, - cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"), - key: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"), - }); - - const caCerts = [ - await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem"), - ]; - const [clientConn, serverConn] = await Promise.all([ - Deno.connectTls({ hostname, port, caCerts }), - listener.accept(), - ]); - await Promise.all([clientConn.handshake(), serverConn.handshake()]); - - const buf = new Uint8Array(128); - const readPromise = serverConn.read(buf); - assertThrows(() => Deno.serveHttp(serverConn), Deno.errors.BadResource); - - clientConn.close(); - listener.close(); - await readPromise; - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - async function httpServerWithoutExclusiveAccessToUnixSocket() { - const filePath = tmpUnixSocketPath(); - const listener = Deno.listen({ path: filePath, transport: "unix" }); - - const [clientConn, serverConn] = await Promise.all([ - Deno.connect({ path: filePath, transport: "unix" }), - listener.accept(), - ]); - - const buf = new Uint8Array(128); - const readPromise = serverConn.read(buf); - assertThrows(() => Deno.serveHttp(serverConn), Deno.errors.BadResource); - - clientConn.close(); - listener.close(); - await readPromise; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerRequestResponseClone() { - const body = "deno".repeat(64 * 1024); - let httpConn: Deno.HttpConn; - const listener = Deno.listen({ port: listenPort }); - const promise = (async () => { - const conn = await listener.accept(); - listener.close(); - httpConn = Deno.serveHttp(conn); - const reqEvent = await httpConn.nextRequest(); - assert(reqEvent); - const { request, respondWith } = reqEvent; - const clone = request.clone(); - const reader = clone.body!.getReader(); - - // get first chunk from branch2 - const clonedChunks = []; - const { value, done } = await reader.read(); - assert(!done); - clonedChunks.push(value); - - // consume request after first chunk single read - // readAll should read correctly the rest of the body. - // firstChunk should be in the stream internal buffer - const body1 = await request.text(); - - while (true) { - const { value, done } = await reader.read(); - if (done) break; - clonedChunks.push(value); - } - let offset = 0; - const body2 = new Uint8Array(body.length); - for (const chunk of clonedChunks) { - body2.set(chunk, offset); - offset += chunk.byteLength; - } - - assertEquals(body1, body); - assertEquals(body1, new TextDecoder().decode(body2)); - await respondWith(new Response(body)); - })(); - - const response = await fetch(`http://localhost:${listenPort}`, { - body, - method: "POST", - }); - const clone = response.clone(); - assertEquals(await response.text(), await clone.text()); - - await promise; - httpConn!.close(); - }, -); - -Deno.test({ - name: "http server compresses and flushes each chunk of a streamed resource", - permissions: { net: true, run: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - const port2 = listenPort2; - - const encoder = new TextEncoder(); - const listener = Deno.listen({ hostname, port }); - const listener2 = Deno.listen({ hostname, port: port2 }); - - let httpConn: Deno.HttpConn; - async function server() { - const tcpConn = await listener.accept(); - httpConn = Deno.serveHttp(tcpConn); - const e = await httpConn.nextRequest(); - assert(e); - const { request, respondWith } = e; - assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br"); - const resp = await fetch(`http://${hostname}:${port2}/`); - await respondWith(resp); - listener.close(); - } - - const ts = new TransformStream(); - const writer = ts.writable.getWriter(); - writer.write(encoder.encode("hello")); - - let httpConn2: Deno.HttpConn; - async function server2() { - const tcpConn = await listener2.accept(); - httpConn2 = Deno.serveHttp(tcpConn); - const e = await httpConn2.nextRequest(); - assert(e); - await e.respondWith( - new Response(ts.readable, { - headers: { "Content-Type": "text/plain" }, - }), - ); - listener2.close(); - } - - async function client() { - const url = `http://${hostname}:${port}/`; - const args = [ - "--request", - "GET", - "--url", - url, - "--header", - "Accept-Encoding: gzip, deflate, br", - "--no-buffer", - ]; - const proc = new Deno.Command("curl", { - args, - stderr: "null", - stdout: "piped", - }).spawn(); - const stdout = proc.stdout - .pipeThrough(new DecompressionStream("gzip")) - .pipeThrough(new TextDecoderStream()); - let body = ""; - for await (const chunk of stdout) { - body += chunk; - if (body === "hello") { - writer.write(encoder.encode(" world")); - writer.close(); - } - } - assertEquals(body, "hello world"); - const status = await proc.status; - assert(status.success); - } - - await Promise.all([server(), server2(), client()]); - httpConn!.close(); - httpConn2!.close(); - }, -}); - -Deno.test("case insensitive comma value finder", async (t) => { - const cases = /** @type {[string, boolean][]} */ ([ - ["websocket", true], - ["wEbSOcKET", true], - [",wEbSOcKET", true], - [",wEbSOcKET,", true], - [", wEbSOcKET ,", true], - ["test, wEbSOcKET ,", true], - ["test ,\twEbSOcKET\t\t ,", true], - ["test , wEbSOcKET", true], - ["test, asdf,web,wEbSOcKET", true], - ["test, asdf,web,wEbSOcKETs", false], - ["test, asdf,awebsocket,wEbSOcKETs", false], - ]); - - const findValue = buildCaseInsensitiveCommaValueFinder("websocket"); - for (const [input, expected] of cases) { - await t.step(input.toString(), () => { - const actual = findValue(input); - assertEquals(actual, expected); - }); - } -}); - -async function httpServerWithErrorBody( - listener: Deno.Listener, - compression: boolean, -): Promise<Deno.HttpConn> { - const conn = await listener.accept(); - listener.close(); - const httpConn = Deno.serveHttp(conn); - const e = await httpConn.nextRequest(); - assert(e); - const { respondWith } = e; - const originalErr = new Error("boom"); - const rs = new ReadableStream({ - async start(controller) { - controller.enqueue(new Uint8Array([65])); - await delay(1000); - controller.error(originalErr); - }, - }); - const init = compression ? { headers: { "content-type": "text/plain" } } : {}; - const response = new Response(rs, init); - const err = await assertRejects(() => respondWith(response)); - assert(err === originalErr); - return httpConn; -} - -for (const compression of [true, false]) { - Deno.test({ - name: `http server errors stream if response body errors (http/1.1${ - compression ? " + compression" : "" - })`, - permissions: { net: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - - const listener = Deno.listen({ hostname, port }); - const server = httpServerWithErrorBody(listener, compression); - - const conn = await Deno.connect({ hostname, port }); - const msg = new TextEncoder().encode( - `GET / HTTP/1.1\r\nHost: ${hostname}:${port}\r\n\r\n`, - ); - const nwritten = await conn.write(msg); - assertEquals(nwritten, msg.byteLength); - - const buf = new Uint8Array(1024); - const nread = await conn.read(buf); - assert(nread); - const data = new TextDecoder().decode(buf.subarray(0, nread)); - assert(data.endsWith("1\r\nA\r\n")); - const nread2 = await conn.read(buf); // connection should be closed now because the stream errored - assertEquals(nread2, null); - conn.close(); - - const httpConn = await server; - httpConn.close(); - }, - }); - - Deno.test({ - name: `http server errors stream if response body errors (http/1.1 + fetch${ - compression ? " + compression" : "" - })`, - permissions: { net: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - - const listener = Deno.listen({ hostname, port }); - const server = httpServerWithErrorBody(listener, compression); - - const resp = await fetch(`http://${hostname}:${port}/`); - assert(resp.body); - const reader = resp.body.getReader(); - const result = await reader.read(); - assert(!result.done); - assertEquals(result.value, new Uint8Array([65])); - const err = await assertRejects(() => reader.read()); - assert(err instanceof TypeError); - assert(err.message.includes("unexpected EOF")); - - const httpConn = await server; - httpConn.close(); - }, - }); - - Deno.test({ - name: `http server errors stream if response body errors (http/2 + fetch${ - compression ? " + compression" : "" - }))`, - permissions: { net: true, read: true }, - async fn() { - const hostname = "localhost"; - const port = listenPort; - - const listener = Deno.listenTls({ - hostname, - port, - cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"), - key: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"), - alpnProtocols: ["h2"], - }); - const server = httpServerWithErrorBody(listener, compression); - - const caCert = Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem"); - const client = Deno.createHttpClient({ caCerts: [caCert] }); - const resp = await fetch(`https://${hostname}:${port}/`, { client }); - client.close(); - assert(resp.body); - const reader = resp.body.getReader(); - const result = await reader.read(); - assert(!result.done); - assertEquals(result.value, new Uint8Array([65])); - const err = await assertRejects(() => reader.read()); - assert(err instanceof TypeError); - assert(err.message.includes("unexpected internal error encountered")); - - const httpConn = await server; - httpConn.close(); - }, - }); -} - -Deno.test({ - name: "request signal is aborted when response errors", - permissions: { net: true }, - async fn() { - let httpConn: Deno.HttpConn; - const promise = (async () => { - const listener = Deno.listen({ port: listenPort }); - const conn = await listener.accept(); - listener.close(); - httpConn = Deno.serveHttp(conn); - const ev = await httpConn.nextRequest(); - const { request, respondWith } = ev!; - - await delay(300); - await assertRejects(() => respondWith(new Response("Hello World"))); - assert(request.signal.aborted); - })(); - - const abortController = new AbortController(); - - fetch(`http://127.0.0.1:${listenPort}/`, { - signal: abortController.signal, - }).catch(() => { - // ignore - }); - - await delay(100); - abortController.abort(); - await promise; - httpConn!.close(); - }, -}); - -Deno.test( - async function httpConnExplicitResourceManagement() { - let promise; - - { - const listen = Deno.listen({ port: listenPort }); - promise = fetch(`http://localhost:${listenPort}/`).catch(() => null); - const serverConn = await listen.accept(); - listen.close(); - - using _httpConn = Deno.serveHttp(serverConn); - } - - const response = await promise; - assertEquals(response, null); - }, -); - -function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader { - // Based on https://tools.ietf.org/html/rfc2616#section-19.4.6 - const tp = new TextProtoReader(r); - let finished = false; - const chunks: Array<{ - offset: number; - data: Uint8Array; - }> = []; - async function read(buf: Uint8Array): Promise<number | null> { - if (finished) return null; - const [chunk] = chunks; - if (chunk) { - const chunkRemaining = chunk.data.byteLength - chunk.offset; - const readLength = Math.min(chunkRemaining, buf.byteLength); - for (let i = 0; i < readLength; i++) { - buf[i] = chunk.data[chunk.offset + i]; - } - chunk.offset += readLength; - if (chunk.offset === chunk.data.byteLength) { - chunks.shift(); - // Consume \r\n; - if ((await tp.readLine()) === null) { - throw new Deno.errors.UnexpectedEof(); - } - } - return readLength; - } - const line = await tp.readLine(); - if (line === null) throw new Deno.errors.UnexpectedEof(); - // TODO(bartlomieju): handle chunk extension - const [chunkSizeString] = line.split(";"); - const chunkSize = parseInt(chunkSizeString, 16); - if (Number.isNaN(chunkSize) || chunkSize < 0) { - throw new Deno.errors.InvalidData("Invalid chunk size"); - } - if (chunkSize > 0) { - if (chunkSize > buf.byteLength) { - let eof = await r.readFull(buf); - if (eof === null) { - throw new Deno.errors.UnexpectedEof(); - } - const restChunk = new Uint8Array(chunkSize - buf.byteLength); - eof = await r.readFull(restChunk); - if (eof === null) { - throw new Deno.errors.UnexpectedEof(); - } else { - chunks.push({ - offset: 0, - data: restChunk, - }); - } - return buf.byteLength; - } else { - const bufToFill = buf.subarray(0, chunkSize); - const eof = await r.readFull(bufToFill); - if (eof === null) { - throw new Deno.errors.UnexpectedEof(); - } - // Consume \r\n - if ((await tp.readLine()) === null) { - throw new Deno.errors.UnexpectedEof(); - } - return chunkSize; - } - } else { - assert(chunkSize === 0); - // Consume \r\n - if ((await r.readLine()) === null) { - throw new Deno.errors.UnexpectedEof(); - } - await readTrailers(h, r); - finished = true; - return null; - } - } - return { read }; -} - -async function readTrailers( - headers: Headers, - r: BufReader, -) { - const trailers = parseTrailer(headers.get("trailer")); - if (trailers == null) return; - const trailerNames = [...trailers.keys()]; - const tp = new TextProtoReader(r); - const result = await tp.readMimeHeader(); - if (result == null) { - throw new Deno.errors.InvalidData("Missing trailer header."); - } - const undeclared = [...result.keys()].filter( - (k) => !trailerNames.includes(k), - ); - if (undeclared.length > 0) { - throw new Deno.errors.InvalidData( - `Undeclared trailers: ${Deno.inspect(undeclared)}.`, - ); - } - for (const [k, v] of result) { - headers.append(k, v); - } - const missingTrailers = trailerNames.filter((k) => !result.has(k)); - if (missingTrailers.length > 0) { - throw new Deno.errors.InvalidData( - `Missing trailers: ${Deno.inspect(missingTrailers)}.`, - ); - } - headers.delete("trailer"); -} - -function parseTrailer(field: string | null): Headers | undefined { - if (field == null) { - return undefined; - } - const trailerNames = field.split(",").map((v) => v.trim().toLowerCase()); - if (trailerNames.length === 0) { - throw new Deno.errors.InvalidData("Empty trailer header."); - } - const prohibited = trailerNames.filter((k) => isProhibitedForTrailer(k)); - if (prohibited.length > 0) { - throw new Deno.errors.InvalidData( - `Prohibited trailer names: ${Deno.inspect(prohibited)}.`, - ); - } - return new Headers(trailerNames.map((key) => [key, ""])); -} - -function isProhibitedForTrailer(key: string): boolean { - const s = new Set(["transfer-encoding", "content-length", "trailer"]); - return s.has(key.toLowerCase()); -} diff --git a/cli/tests/unit/image_bitmap_test.ts b/cli/tests/unit/image_bitmap_test.ts deleted file mode 100644 index 364f2a167..000000000 --- a/cli/tests/unit/image_bitmap_test.ts +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assertEquals } from "./test_util.ts"; - -function generateNumberedData(n: number): Uint8ClampedArray { - return new Uint8ClampedArray( - Array.from({ length: n }, (_, i) => [i + 1, 0, 0, 1]).flat(), - ); -} - -Deno.test(async function imageBitmapDirect() { - const data = generateNumberedData(3); - const imageData = new ImageData(data, 3, 1); - const imageBitmap = await createImageBitmap(imageData); - assertEquals( - // @ts-ignore: Deno[Deno.internal].core allowed - Deno[Deno.internal].getBitmapData(imageBitmap), - new Uint8Array(data.buffer), - ); -}); - -Deno.test(async function imageBitmapCrop() { - const data = generateNumberedData(3 * 3); - const imageData = new ImageData(data, 3, 3); - const imageBitmap = await createImageBitmap(imageData, 1, 1, 1, 1); - assertEquals( - // @ts-ignore: Deno[Deno.internal].core allowed - Deno[Deno.internal].getBitmapData(imageBitmap), - new Uint8Array([5, 0, 0, 1]), - ); -}); - -Deno.test(async function imageBitmapCropPartialNegative() { - const data = generateNumberedData(3 * 3); - const imageData = new ImageData(data, 3, 3); - const imageBitmap = await createImageBitmap(imageData, -1, -1, 2, 2); - // @ts-ignore: Deno[Deno.internal].core allowed - // deno-fmt-ignore - assertEquals(Deno[Deno.internal].getBitmapData(imageBitmap), new Uint8Array([ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, 0, 1 - ])); -}); - -Deno.test(async function imageBitmapCropGreater() { - const data = generateNumberedData(3 * 3); - const imageData = new ImageData(data, 3, 3); - const imageBitmap = await createImageBitmap(imageData, -1, -1, 5, 5); - // @ts-ignore: Deno[Deno.internal].core allowed - // deno-fmt-ignore - assertEquals(Deno[Deno.internal].getBitmapData(imageBitmap), new Uint8Array([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 4, 0, 0, 1, 5, 0, 0, 1, 6, 0, 0, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 7, 0, 0, 1, 8, 0, 0, 1, 9, 0, 0, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ])); -}); - -Deno.test(async function imageBitmapScale() { - const data = generateNumberedData(3); - const imageData = new ImageData(data, 3, 1); - const imageBitmap = await createImageBitmap(imageData, { - resizeHeight: 5, - resizeWidth: 5, - resizeQuality: "pixelated", - }); - // @ts-ignore: Deno[Deno.internal].core allowed - // deno-fmt-ignore - assertEquals(Deno[Deno.internal].getBitmapData(imageBitmap), new Uint8Array([ - 1, 0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1, 3, 0, 0, 1, - 1, 0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1, 3, 0, 0, 1, - 1, 0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1, 3, 0, 0, 1, - 1, 0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1, 3, 0, 0, 1, - 1, 0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1, 3, 0, 0, 1 - ])); -}); - -Deno.test(async function imageBitmapFlipY() { - const data = generateNumberedData(9); - const imageData = new ImageData(data, 3, 3); - const imageBitmap = await createImageBitmap(imageData, { - imageOrientation: "flipY", - }); - // @ts-ignore: Deno[Deno.internal].core allowed - // deno-fmt-ignore - assertEquals(Deno[Deno.internal].getBitmapData(imageBitmap), new Uint8Array([ - 7, 0, 0, 1, 8, 0, 0, 1, 9, 0, 0, 1, - 4, 0, 0, 1, 5, 0, 0, 1, 6, 0, 0, 1, - 1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1, - ])); -}); diff --git a/cli/tests/unit/image_data_test.ts b/cli/tests/unit/image_data_test.ts deleted file mode 100644 index 7156301a0..000000000 --- a/cli/tests/unit/image_data_test.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assertEquals } from "./test_util.ts"; - -Deno.test(function imageDataInitializedWithSourceWidthAndHeight() { - const imageData = new ImageData(16, 9); - - assertEquals(imageData.width, 16); - assertEquals(imageData.height, 9); - assertEquals(imageData.data.length, 16 * 9 * 4); // width * height * 4 (RGBA pixels) - assertEquals(imageData.colorSpace, "srgb"); -}); - -Deno.test(function imageDataInitializedWithImageDataAndWidth() { - const imageData = new ImageData(new Uint8ClampedArray(16 * 9 * 4), 16); - - assertEquals(imageData.width, 16); - assertEquals(imageData.height, 9); - assertEquals(imageData.data.length, 16 * 9 * 4); // width * height * 4 (RGBA pixels) - assertEquals(imageData.colorSpace, "srgb"); -}); - -Deno.test( - function imageDataInitializedWithImageDataAndWidthAndHeightAndColorSpace() { - const imageData = new ImageData(new Uint8ClampedArray(16 * 9 * 4), 16, 9, { - colorSpace: "display-p3", - }); - - assertEquals(imageData.width, 16); - assertEquals(imageData.height, 9); - assertEquals(imageData.data.length, 16 * 9 * 4); // width * height * 4 (RGBA pixels) - assertEquals(imageData.colorSpace, "display-p3"); - }, -); - -Deno.test( - async function imageDataUsedInWorker() { - const { promise, resolve } = Promise.withResolvers<void>(); - const url = import.meta.resolve( - "../testdata/workers/image_data_worker.ts", - ); - const expectedData = 16; - - const worker = new Worker(url, { type: "module" }); - worker.onmessage = function (e) { - assertEquals(expectedData, e.data); - worker.terminate(); - resolve(); - }; - - await promise; - }, -); diff --git a/cli/tests/unit/internals_test.ts b/cli/tests/unit/internals_test.ts deleted file mode 100644 index bb4c21793..000000000 --- a/cli/tests/unit/internals_test.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert } from "./test_util.ts"; - -Deno.test(function internalsExists() { - const { - inspectArgs, - // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol - } = Deno[Deno.internal]; - assert(!!inspectArgs); -}); diff --git a/cli/tests/unit/intl_test.ts b/cli/tests/unit/intl_test.ts deleted file mode 100644 index 6e4de378c..000000000 --- a/cli/tests/unit/intl_test.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "./test_util.ts"; - -Deno.test("Intl.v8BreakIterator should be undefined", () => { - // @ts-expect-error Intl.v8BreakIterator is not a standard API - assertEquals(Intl.v8BreakIterator, undefined); -}); diff --git a/cli/tests/unit/io_test.ts b/cli/tests/unit/io_test.ts deleted file mode 100644 index 04c9dab4b..000000000 --- a/cli/tests/unit/io_test.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "./test_util.ts"; -import { Buffer } from "@test_util/std/io/buffer.ts"; - -const DEFAULT_BUF_SIZE = 32 * 1024; - -type Spy = { calls: number }; - -function repeat(c: string, bytes: number): Uint8Array { - assertEquals(c.length, 1); - const ui8 = new Uint8Array(bytes); - ui8.fill(c.charCodeAt(0)); - return ui8; -} - -function spyRead(obj: Buffer): Spy { - const spy: Spy = { - calls: 0, - }; - - const orig = obj.read.bind(obj); - - obj.read = (p: Uint8Array): Promise<number | null> => { - spy.calls++; - return orig(p); - }; - - return spy; -} - -Deno.test(async function copyWithDefaultBufferSize() { - const xBytes = repeat("b", DEFAULT_BUF_SIZE); - const reader = new Buffer(xBytes.buffer as ArrayBuffer); - const write = new Buffer(); - - const readSpy = spyRead(reader); - - // deno-lint-ignore no-deprecated-deno-api - const n = await Deno.copy(reader, write); - - assertEquals(n, xBytes.length); - assertEquals(write.length, xBytes.length); - assertEquals(readSpy.calls, 2); // read with DEFAULT_BUF_SIZE bytes + read with 0 bytes -}); - -Deno.test(async function copyWithCustomBufferSize() { - const bufSize = 1024; - const xBytes = repeat("b", DEFAULT_BUF_SIZE); - const reader = new Buffer(xBytes.buffer as ArrayBuffer); - const write = new Buffer(); - - const readSpy = spyRead(reader); - - // deno-lint-ignore no-deprecated-deno-api - const n = await Deno.copy(reader, write, { bufSize }); - - assertEquals(n, xBytes.length); - assertEquals(write.length, xBytes.length); - assertEquals(readSpy.calls, DEFAULT_BUF_SIZE / bufSize + 1); -}); - -Deno.test({ permissions: { write: true } }, async function copyBufferToFile() { - const filePath = "test-file.txt"; - // bigger than max File possible buffer 16kb - const bufSize = 32 * 1024; - const xBytes = repeat("b", bufSize); - const reader = new Buffer(xBytes.buffer as ArrayBuffer); - const write = await Deno.open(filePath, { write: true, create: true }); - - // deno-lint-ignore no-deprecated-deno-api - const n = await Deno.copy(reader, write, { bufSize }); - - assertEquals(n, xBytes.length); - - write.close(); - await Deno.remove(filePath); -}); diff --git a/cli/tests/unit/jupyter_test.ts b/cli/tests/unit/jupyter_test.ts deleted file mode 100644 index 07defe230..000000000 --- a/cli/tests/unit/jupyter_test.ts +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assertEquals, assertThrows } from "./test_util.ts"; - -// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol -const format = Deno[Deno.internal].jupyter.formatInner; - -Deno.test("Deno.jupyter is not available", () => { - assertThrows( - () => Deno.jupyter, - "Deno.jupyter is only available in `deno jupyter` subcommand.", - ); -}); - -export async function assertFormattedAs(obj: unknown, result: object) { - const formatted = await format(obj); - assertEquals(formatted, result); -} - -Deno.test("display(canvas) creates a PNG", async () => { - // Let's make a fake Canvas with a fake Data URL - class FakeCanvas { - toDataURL() { - return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAAAVSURBVAiZY/zPwPCfAQ0woQtQQRAAzqkCCB/D3o0AAAAASUVORK5CYII="; - } - } - const canvas = new FakeCanvas(); - - await assertFormattedAs(canvas, { - "image/png": - "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAAAVSURBVAiZY/zPwPCfAQ0woQtQQRAAzqkCCB/D3o0AAAAASUVORK5CYII=", - }); -}); - -Deno.test( - "class with a Symbol.for('Jupyter.display') function gets displayed", - async () => { - class Example { - x: number; - - constructor(x: number) { - this.x = x; - } - - [Symbol.for("Jupyter.display")]() { - return { "application/json": { x: this.x } }; - } - } - - const example = new Example(5); - - // Now to check on the broadcast call being made - await assertFormattedAs(example, { "application/json": { x: 5 } }); - }, -); - -Deno.test( - "class with an async Symbol.for('Jupyter.display') function gets displayed", - async () => { - class Example { - x: number; - - constructor(x: number) { - this.x = x; - } - - async [Symbol.for("Jupyter.display")]() { - await new Promise((resolve) => setTimeout(resolve, 0)); - - return { "application/json": { x: this.x } }; - } - } - - const example = new Example(3); - - // Now to check on the broadcast call being made - await assertFormattedAs(example, { "application/json": { x: 3 } }); - }, -); diff --git a/cli/tests/unit/kv_queue_test.ts b/cli/tests/unit/kv_queue_test.ts deleted file mode 100644 index e052dcbf7..000000000 --- a/cli/tests/unit/kv_queue_test.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals, assertFalse } from "./test_util.ts"; - -Deno.test({}, async function queueTestDbClose() { - const db: Deno.Kv = await Deno.openKv(":memory:"); - db.close(); - try { - await db.listenQueue(() => {}); - assertFalse(false); - } catch (e) { - assertEquals(e.message, "already closed"); - } -}); diff --git a/cli/tests/unit/kv_queue_test_no_db_close.ts b/cli/tests/unit/kv_queue_test_no_db_close.ts deleted file mode 100644 index 947e1c5e6..000000000 --- a/cli/tests/unit/kv_queue_test_no_db_close.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals, assertNotEquals } from "./test_util.ts"; - -Deno.test({ - sanitizeOps: false, - sanitizeResources: false, -}, async function queueTestNoDbClose() { - const db: Deno.Kv = await Deno.openKv(":memory:"); - const { promise, resolve } = Promise.withResolvers<void>(); - let dequeuedMessage: unknown = null; - db.listenQueue((msg) => { - dequeuedMessage = msg; - resolve(); - }); - const res = await db.enqueue("test"); - assert(res.ok); - assertNotEquals(res.versionstamp, null); - await promise; - assertEquals(dequeuedMessage, "test"); -}); diff --git a/cli/tests/unit/kv_queue_undelivered_test.ts b/cli/tests/unit/kv_queue_undelivered_test.ts deleted file mode 100644 index 1fcefe7e2..000000000 --- a/cli/tests/unit/kv_queue_undelivered_test.ts +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "./test_util.ts"; - -const sleep = (time: number) => new Promise((r) => setTimeout(r, time)); - -// TODO(igorzi): https://github.com/denoland/deno/issues/21437 -// let isCI: boolean; -// try { -// isCI = Deno.env.get("CI") !== undefined; -// } catch { -// isCI = true; -// } - -function queueTest(name: string, fn: (db: Deno.Kv) => Promise<void>) { - // TODO(igorzi): https://github.com/denoland/deno/issues/21437 - Deno.test.ignore({ - name, - // https://github.com/denoland/deno/issues/18363 - // ignore: Deno.build.os === "darwin" && isCI, - async fn() { - const db: Deno.Kv = await Deno.openKv( - ":memory:", - ); - await fn(db); - }, - }); -} - -async function collect<T>( - iter: Deno.KvListIterator<T>, -): Promise<Deno.KvEntry<T>[]> { - const entries: Deno.KvEntry<T>[] = []; - for await (const entry of iter) { - entries.push(entry); - } - return entries; -} - -queueTest("queue with undelivered", async (db) => { - const listener = db.listenQueue((_msg) => { - throw new TypeError("dequeue error"); - }); - try { - await db.enqueue("test", { - keysIfUndelivered: [["queue_failed", "a"], ["queue_failed", "b"]], - backoffSchedule: [10, 20], - }); - await sleep(3000); - const undelivered = await collect(db.list({ prefix: ["queue_failed"] })); - assertEquals(undelivered.length, 2); - assertEquals(undelivered[0].key, ["queue_failed", "a"]); - assertEquals(undelivered[0].value, "test"); - assertEquals(undelivered[1].key, ["queue_failed", "b"]); - assertEquals(undelivered[1].value, "test"); - } finally { - db.close(); - await listener; - } -}); diff --git a/cli/tests/unit/kv_test.ts b/cli/tests/unit/kv_test.ts deleted file mode 100644 index 5780d9900..000000000 --- a/cli/tests/unit/kv_test.ts +++ /dev/null @@ -1,2321 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - AssertionError, - assertNotEquals, - assertRejects, - assertThrows, -} from "./test_util.ts"; -import { assertType, IsExact } from "@test_util/std/testing/types.ts"; - -const sleep = (time: number) => new Promise((r) => setTimeout(r, time)); - -let isCI: boolean; -try { - isCI = Deno.env.get("CI") !== undefined; -} catch { - isCI = true; -} - -// Defined in test_util/src/lib.rs -Deno.env.set("DENO_KV_ACCESS_TOKEN", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - -Deno.test({ - name: "openKv :memory: no permissions", - permissions: {}, - async fn() { - const db = await Deno.openKv(":memory:"); - await db.close(); - }, -}); - -Deno.test({ - name: "openKv invalid filenames", - permissions: {}, - async fn() { - await assertRejects( - async () => await Deno.openKv(""), - TypeError, - "Filename cannot be empty", - ); - await assertRejects( - async () => await Deno.openKv(":foo"), - TypeError, - "Filename cannot start with ':' unless prefixed with './'", - ); - }, -}); - -function dbTest(name: string, fn: (db: Deno.Kv) => Promise<void> | void) { - Deno.test({ - name, - // https://github.com/denoland/deno/issues/18363 - ignore: Deno.build.os === "darwin" && isCI, - async fn() { - const db: Deno.Kv = await Deno.openKv(":memory:"); - try { - await fn(db); - } finally { - db.close(); - } - }, - }); -} - -function queueTest(name: string, fn: (db: Deno.Kv) => Promise<void>) { - Deno.test({ - name, - // https://github.com/denoland/deno/issues/18363 - ignore: Deno.build.os === "darwin" && isCI, - async fn() { - const db: Deno.Kv = await Deno.openKv(":memory:"); - await fn(db); - }, - }); -} - -const ZERO_VERSIONSTAMP = "00000000000000000000"; - -dbTest("basic read-write-delete and versionstamps", async (db) => { - const result1 = await db.get(["a"]); - assertEquals(result1.key, ["a"]); - assertEquals(result1.value, null); - assertEquals(result1.versionstamp, null); - - const setRes = await db.set(["a"], "b"); - assert(setRes.ok); - assert(setRes.versionstamp > ZERO_VERSIONSTAMP); - const result2 = await db.get(["a"]); - assertEquals(result2.key, ["a"]); - assertEquals(result2.value, "b"); - assertEquals(result2.versionstamp, setRes.versionstamp); - - const setRes2 = await db.set(["a"], "c"); - assert(setRes2.ok); - assert(setRes2.versionstamp > setRes.versionstamp); - const result3 = await db.get(["a"]); - assertEquals(result3.key, ["a"]); - assertEquals(result3.value, "c"); - assertEquals(result3.versionstamp, setRes2.versionstamp); - - await db.delete(["a"]); - const result4 = await db.get(["a"]); - assertEquals(result4.key, ["a"]); - assertEquals(result4.value, null); - assertEquals(result4.versionstamp, null); -}); - -const VALUE_CASES = [ - { name: "string", value: "hello" }, - { name: "number", value: 42 }, - { name: "bigint", value: 42n }, - { name: "boolean", value: true }, - { name: "null", value: null }, - { name: "undefined", value: undefined }, - { name: "Date", value: new Date(0) }, - { name: "Uint8Array", value: new Uint8Array([1, 2, 3]) }, - { name: "ArrayBuffer", value: new ArrayBuffer(3) }, - { name: "array", value: [1, 2, 3] }, - { name: "object", value: { a: 1, b: 2 } }, - { name: "nested array", value: [[1, 2], [3, 4]] }, - { name: "nested object", value: { a: { b: 1 } } }, -]; - -for (const { name, value } of VALUE_CASES) { - dbTest(`set and get ${name} value`, async (db) => { - await db.set(["a"], value); - const result = await db.get(["a"]); - assertEquals(result.key, ["a"]); - assertEquals(result.value, value); - }); -} - -dbTest("set and get recursive object", async (db) => { - // deno-lint-ignore no-explicit-any - const value: any = { a: undefined }; - value.a = value; - await db.set(["a"], value); - const result = await db.get(["a"]); - assertEquals(result.key, ["a"]); - // deno-lint-ignore no-explicit-any - const resultValue: any = result.value; - assert(resultValue.a === resultValue); -}); - -// invalid values (as per structured clone algorithm with _for storage_, NOT JSON) -const INVALID_VALUE_CASES = [ - { name: "function", value: () => {} }, - { name: "symbol", value: Symbol() }, - { name: "WeakMap", value: new WeakMap() }, - { name: "WeakSet", value: new WeakSet() }, - { - name: "WebAssembly.Module", - value: new WebAssembly.Module( - new Uint8Array([0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00]), - ), - }, - { - name: "SharedArrayBuffer", - value: new SharedArrayBuffer(3), - }, -]; - -for (const { name, value } of INVALID_VALUE_CASES) { - dbTest(`set and get ${name} value (invalid)`, async (db) => { - await assertRejects( - async () => await db.set(["a"], value), - Error, - ); - const res = await db.get(["a"]); - assertEquals(res.key, ["a"]); - assertEquals(res.value, null); - }); -} - -const keys = [ - ["a"], - ["a", "b"], - ["a", "b", "c"], - [1], - ["a", 1], - ["a", 1, "b"], - [1n], - ["a", 1n], - ["a", 1n, "b"], - [true], - ["a", true], - ["a", true, "b"], - [new Uint8Array([1, 2, 3])], - ["a", new Uint8Array([1, 2, 3])], - ["a", new Uint8Array([1, 2, 3]), "b"], - [1, 1n, true, new Uint8Array([1, 2, 3]), "a"], -]; - -for (const key of keys) { - dbTest(`set and get ${Deno.inspect(key)} key`, async (db) => { - await db.set(key, "b"); - const result = await db.get(key); - assertEquals(result.key, key); - assertEquals(result.value, "b"); - }); -} - -const INVALID_KEYS = [ - [null], - [undefined], - [], - [{}], - [new Date()], - [new ArrayBuffer(3)], - [new Uint8Array([1, 2, 3]).buffer], - [["a", "b"]], -]; - -for (const key of INVALID_KEYS) { - dbTest(`set and get invalid key ${Deno.inspect(key)}`, async (db) => { - await assertRejects( - async () => { - // @ts-ignore - we are testing invalid keys - await db.set(key, "b"); - }, - Error, - ); - }); -} - -dbTest("compare and mutate", async (db) => { - await db.set(["t"], "1"); - - const currentValue = await db.get(["t"]); - assert(currentValue.versionstamp); - assert(currentValue.versionstamp > ZERO_VERSIONSTAMP); - - let res = await db.atomic() - .check({ key: ["t"], versionstamp: currentValue.versionstamp }) - .set(currentValue.key, "2") - .commit(); - assert(res.ok); - assert(res.versionstamp > currentValue.versionstamp); - - const newValue = await db.get(["t"]); - assertEquals(newValue.versionstamp, res.versionstamp); - assertEquals(newValue.value, "2"); - - res = await db.atomic() - .check({ key: ["t"], versionstamp: currentValue.versionstamp }) - .set(currentValue.key, "3") - .commit(); - assert(!res.ok); - - const newValue2 = await db.get(["t"]); - assertEquals(newValue2.versionstamp, newValue.versionstamp); - assertEquals(newValue2.value, "2"); -}); - -dbTest("compare and mutate not exists", async (db) => { - let res = await db.atomic() - .check({ key: ["t"], versionstamp: null }) - .set(["t"], "1") - .commit(); - assert(res.ok); - assert(res.versionstamp > ZERO_VERSIONSTAMP); - - const newValue = await db.get(["t"]); - assertEquals(newValue.versionstamp, res.versionstamp); - assertEquals(newValue.value, "1"); - - res = await db.atomic() - .check({ key: ["t"], versionstamp: null }) - .set(["t"], "2") - .commit(); - assert(!res.ok); -}); - -dbTest("atomic mutation helper (sum)", async (db) => { - await db.set(["t"], new Deno.KvU64(42n)); - assertEquals((await db.get(["t"])).value, new Deno.KvU64(42n)); - - await db.atomic().sum(["t"], 1n).commit(); - assertEquals((await db.get(["t"])).value, new Deno.KvU64(43n)); -}); - -dbTest("atomic mutation helper (min)", async (db) => { - await db.set(["t"], new Deno.KvU64(42n)); - assertEquals((await db.get(["t"])).value, new Deno.KvU64(42n)); - - await db.atomic().min(["t"], 1n).commit(); - assertEquals((await db.get(["t"])).value, new Deno.KvU64(1n)); - - await db.atomic().min(["t"], 2n).commit(); - assertEquals((await db.get(["t"])).value, new Deno.KvU64(1n)); -}); - -dbTest("atomic mutation helper (max)", async (db) => { - await db.set(["t"], new Deno.KvU64(42n)); - assertEquals((await db.get(["t"])).value, new Deno.KvU64(42n)); - - await db.atomic().max(["t"], 41n).commit(); - assertEquals((await db.get(["t"])).value, new Deno.KvU64(42n)); - - await db.atomic().max(["t"], 43n).commit(); - assertEquals((await db.get(["t"])).value, new Deno.KvU64(43n)); -}); - -dbTest("compare multiple and mutate", async (db) => { - const setRes1 = await db.set(["t1"], "1"); - const setRes2 = await db.set(["t2"], "2"); - assert(setRes1.ok); - assert(setRes1.versionstamp > ZERO_VERSIONSTAMP); - assert(setRes2.ok); - assert(setRes2.versionstamp > ZERO_VERSIONSTAMP); - - const currentValue1 = await db.get(["t1"]); - assertEquals(currentValue1.versionstamp, setRes1.versionstamp); - const currentValue2 = await db.get(["t2"]); - assertEquals(currentValue2.versionstamp, setRes2.versionstamp); - - const res = await db.atomic() - .check({ key: ["t1"], versionstamp: currentValue1.versionstamp }) - .check({ key: ["t2"], versionstamp: currentValue2.versionstamp }) - .set(currentValue1.key, "3") - .set(currentValue2.key, "4") - .commit(); - assert(res.ok); - assert(res.versionstamp > setRes2.versionstamp); - - const newValue1 = await db.get(["t1"]); - assertEquals(newValue1.versionstamp, res.versionstamp); - assertEquals(newValue1.value, "3"); - const newValue2 = await db.get(["t2"]); - assertEquals(newValue2.versionstamp, res.versionstamp); - assertEquals(newValue2.value, "4"); - - // just one of the two checks failed - const res2 = await db.atomic() - .check({ key: ["t1"], versionstamp: newValue1.versionstamp }) - .check({ key: ["t2"], versionstamp: null }) - .set(newValue1.key, "5") - .set(newValue2.key, "6") - .commit(); - assert(!res2.ok); - - const newValue3 = await db.get(["t1"]); - assertEquals(newValue3.versionstamp, res.versionstamp); - assertEquals(newValue3.value, "3"); - const newValue4 = await db.get(["t2"]); - assertEquals(newValue4.versionstamp, res.versionstamp); - assertEquals(newValue4.value, "4"); -}); - -dbTest("atomic mutation ordering (set before delete)", async (db) => { - await db.set(["a"], "1"); - const res = await db.atomic() - .set(["a"], "2") - .delete(["a"]) - .commit(); - assert(res.ok); - const result = await db.get(["a"]); - assertEquals(result.value, null); -}); - -dbTest("atomic mutation ordering (delete before set)", async (db) => { - await db.set(["a"], "1"); - const res = await db.atomic() - .delete(["a"]) - .set(["a"], "2") - .commit(); - assert(res.ok); - const result = await db.get(["a"]); - assertEquals(result.value, "2"); -}); - -dbTest("atomic mutation type=set", async (db) => { - const res = await db.atomic() - .mutate({ key: ["a"], value: "1", type: "set" }) - .commit(); - assert(res.ok); - const result = await db.get(["a"]); - assertEquals(result.value, "1"); -}); - -dbTest("atomic mutation type=set overwrite", async (db) => { - await db.set(["a"], "1"); - const res = await db.atomic() - .mutate({ key: ["a"], value: "2", type: "set" }) - .commit(); - assert(res.ok); - const result = await db.get(["a"]); - assertEquals(result.value, "2"); -}); - -dbTest("atomic mutation type=delete", async (db) => { - await db.set(["a"], "1"); - const res = await db.atomic() - .mutate({ key: ["a"], type: "delete" }) - .commit(); - assert(res.ok); - const result = await db.get(["a"]); - assertEquals(result.value, null); -}); - -dbTest("atomic mutation type=delete no exists", async (db) => { - const res = await db.atomic() - .mutate({ key: ["a"], type: "delete" }) - .commit(); - assert(res.ok); - const result = await db.get(["a"]); - assertEquals(result.value, null); -}); - -dbTest("atomic mutation type=sum", async (db) => { - await db.set(["a"], new Deno.KvU64(10n)); - const res = await db.atomic() - .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "sum" }) - .commit(); - assert(res.ok); - const result = await db.get(["a"]); - assertEquals(result.value, new Deno.KvU64(11n)); -}); - -dbTest("atomic mutation type=sum no exists", async (db) => { - const res = await db.atomic() - .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "sum" }) - .commit(); - assert(res.ok); - const result = await db.get(["a"]); - assert(result.value); - assertEquals(result.value, new Deno.KvU64(1n)); -}); - -dbTest("atomic mutation type=sum wrap around", async (db) => { - await db.set(["a"], new Deno.KvU64(0xffffffffffffffffn)); - const res = await db.atomic() - .mutate({ key: ["a"], value: new Deno.KvU64(10n), type: "sum" }) - .commit(); - assert(res.ok); - const result = await db.get(["a"]); - assertEquals(result.value, new Deno.KvU64(9n)); - - const res2 = await db.atomic() - .mutate({ - key: ["a"], - value: new Deno.KvU64(0xffffffffffffffffn), - type: "sum", - }) - .commit(); - assert(res2); - const result2 = await db.get(["a"]); - assertEquals(result2.value, new Deno.KvU64(8n)); -}); - -dbTest("atomic mutation type=sum wrong type in db", async (db) => { - await db.set(["a"], 1); - await assertRejects( - async () => { - await db.atomic() - .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "sum" }) - .commit(); - }, - TypeError, - "Failed to perform 'sum' mutation on a non-U64 value in the database", - ); -}); - -dbTest("atomic mutation type=sum wrong type in mutation", async (db) => { - await db.set(["a"], new Deno.KvU64(1n)); - await assertRejects( - async () => { - await db.atomic() - // @ts-expect-error wrong type is intentional - .mutate({ key: ["a"], value: 1, type: "sum" }) - .commit(); - }, - TypeError, - "Failed to perform 'sum' mutation on a non-U64 operand", - ); -}); - -dbTest("atomic mutation type=min", async (db) => { - await db.set(["a"], new Deno.KvU64(10n)); - const res = await db.atomic() - .mutate({ key: ["a"], value: new Deno.KvU64(5n), type: "min" }) - .commit(); - assert(res.ok); - const result = await db.get(["a"]); - assertEquals(result.value, new Deno.KvU64(5n)); - - const res2 = await db.atomic() - .mutate({ key: ["a"], value: new Deno.KvU64(15n), type: "min" }) - .commit(); - assert(res2); - const result2 = await db.get(["a"]); - assertEquals(result2.value, new Deno.KvU64(5n)); -}); - -dbTest("atomic mutation type=min no exists", async (db) => { - const res = await db.atomic() - .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "min" }) - .commit(); - assert(res.ok); - const result = await db.get(["a"]); - assert(result.value); - assertEquals(result.value, new Deno.KvU64(1n)); -}); - -dbTest("atomic mutation type=min wrong type in db", async (db) => { - await db.set(["a"], 1); - await assertRejects( - async () => { - await db.atomic() - .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "min" }) - .commit(); - }, - TypeError, - "Failed to perform 'min' mutation on a non-U64 value in the database", - ); -}); - -dbTest("atomic mutation type=min wrong type in mutation", async (db) => { - await db.set(["a"], new Deno.KvU64(1n)); - await assertRejects( - async () => { - await db.atomic() - // @ts-expect-error wrong type is intentional - .mutate({ key: ["a"], value: 1, type: "min" }) - .commit(); - }, - TypeError, - "Failed to perform 'min' mutation on a non-U64 operand", - ); -}); - -dbTest("atomic mutation type=max", async (db) => { - await db.set(["a"], new Deno.KvU64(10n)); - const res = await db.atomic() - .mutate({ key: ["a"], value: new Deno.KvU64(5n), type: "max" }) - .commit(); - assert(res.ok); - const result = await db.get(["a"]); - assertEquals(result.value, new Deno.KvU64(10n)); - - const res2 = await db.atomic() - .mutate({ key: ["a"], value: new Deno.KvU64(15n), type: "max" }) - .commit(); - assert(res2); - const result2 = await db.get(["a"]); - assertEquals(result2.value, new Deno.KvU64(15n)); -}); - -dbTest("atomic mutation type=max no exists", async (db) => { - const res = await db.atomic() - .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "max" }) - .commit(); - assert(res.ok); - const result = await db.get(["a"]); - assert(result.value); - assertEquals(result.value, new Deno.KvU64(1n)); -}); - -dbTest("atomic mutation type=max wrong type in db", async (db) => { - await db.set(["a"], 1); - await assertRejects( - async () => { - await db.atomic() - .mutate({ key: ["a"], value: new Deno.KvU64(1n), type: "max" }) - .commit(); - }, - TypeError, - "Failed to perform 'max' mutation on a non-U64 value in the database", - ); -}); - -dbTest("atomic mutation type=max wrong type in mutation", async (db) => { - await db.set(["a"], new Deno.KvU64(1n)); - await assertRejects( - async () => { - await db.atomic() - // @ts-expect-error wrong type is intentional - .mutate({ key: ["a"], value: 1, type: "max" }) - .commit(); - }, - TypeError, - "Failed to perform 'max' mutation on a non-U64 operand", - ); -}); - -Deno.test("KvU64 comparison", () => { - const a = new Deno.KvU64(1n); - const b = new Deno.KvU64(1n); - assertEquals(a, b); - assertThrows(() => { - assertEquals(a, new Deno.KvU64(2n)); - }, AssertionError); -}); - -Deno.test("KvU64 overflow", () => { - assertThrows(() => { - new Deno.KvU64(2n ** 64n); - }, RangeError); -}); - -Deno.test("KvU64 underflow", () => { - assertThrows(() => { - new Deno.KvU64(-1n); - }, RangeError); -}); - -Deno.test("KvU64 unbox", () => { - const a = new Deno.KvU64(1n); - assertEquals(a.value, 1n); -}); - -Deno.test("KvU64 unbox with valueOf", () => { - const a = new Deno.KvU64(1n); - assertEquals(a.valueOf(), 1n); -}); - -Deno.test("KvU64 auto-unbox", () => { - const a = new Deno.KvU64(1n); - assertEquals(a as unknown as bigint + 1n, 2n); -}); - -Deno.test("KvU64 toString", () => { - const a = new Deno.KvU64(1n); - assertEquals(a.toString(), "1"); -}); - -Deno.test("KvU64 inspect", () => { - const a = new Deno.KvU64(1n); - assertEquals(Deno.inspect(a), "[Deno.KvU64: 1n]"); -}); - -async function collect<T>( - iter: Deno.KvListIterator<T>, -): Promise<Deno.KvEntry<T>[]> { - const entries: Deno.KvEntry<T>[] = []; - for await (const entry of iter) { - entries.push(entry); - } - return entries; -} - -async function setupData(db: Deno.Kv): Promise<string> { - const res = await db.atomic() - .set(["a"], -1) - .set(["a", "a"], 0) - .set(["a", "b"], 1) - .set(["a", "c"], 2) - .set(["a", "d"], 3) - .set(["a", "e"], 4) - .set(["b"], 99) - .set(["b", "a"], 100) - .commit(); - assert(res.ok); - return res.versionstamp; -} - -dbTest("get many", async (db) => { - const versionstamp = await setupData(db); - const entries = await db.getMany([["b", "a"], ["a"], ["c"]]); - assertEquals(entries, [ - { key: ["b", "a"], value: 100, versionstamp }, - { key: ["a"], value: -1, versionstamp }, - { key: ["c"], value: null, versionstamp: null }, - ]); -}); - -dbTest("list prefix", async (db) => { - const versionstamp = await setupData(db); - const entries = await collect(db.list({ prefix: ["a"] })); - assertEquals(entries, [ - { key: ["a", "a"], value: 0, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - { key: ["a", "e"], value: 4, versionstamp }, - ]); -}); - -dbTest("list prefix empty", async (db) => { - await setupData(db); - const entries = await collect(db.list({ prefix: ["c"] })); - assertEquals(entries.length, 0); - - const entries2 = await collect(db.list({ prefix: ["a", "f"] })); - assertEquals(entries2.length, 0); -}); - -dbTest("list prefix with start", async (db) => { - const versionstamp = await setupData(db); - const entries = await collect(db.list({ prefix: ["a"], start: ["a", "c"] })); - assertEquals(entries, [ - { key: ["a", "c"], value: 2, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - { key: ["a", "e"], value: 4, versionstamp }, - ]); -}); - -dbTest("list prefix with start empty", async (db) => { - await setupData(db); - const entries = await collect(db.list({ prefix: ["a"], start: ["a", "f"] })); - assertEquals(entries.length, 0); -}); - -dbTest("list prefix with start equal to prefix", async (db) => { - await setupData(db); - await assertRejects( - async () => await collect(db.list({ prefix: ["a"], start: ["a"] })), - TypeError, - "start key is not in the keyspace defined by prefix", - ); -}); - -dbTest("list prefix with start out of bounds", async (db) => { - await setupData(db); - await assertRejects( - async () => await collect(db.list({ prefix: ["b"], start: ["a"] })), - TypeError, - "start key is not in the keyspace defined by prefix", - ); -}); - -dbTest("list prefix with end", async (db) => { - const versionstamp = await setupData(db); - const entries = await collect(db.list({ prefix: ["a"], end: ["a", "c"] })); - assertEquals(entries, [ - { key: ["a", "a"], value: 0, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - ]); -}); - -dbTest("list prefix with end empty", async (db) => { - await setupData(db); - const entries = await collect(db.list({ prefix: ["a"], end: ["a", "a"] })); - assertEquals(entries.length, 0); -}); - -dbTest("list prefix with end equal to prefix", async (db) => { - await setupData(db); - await assertRejects( - async () => await collect(db.list({ prefix: ["a"], end: ["a"] })), - TypeError, - "end key is not in the keyspace defined by prefix", - ); -}); - -dbTest("list prefix with end out of bounds", async (db) => { - await setupData(db); - await assertRejects( - async () => await collect(db.list({ prefix: ["a"], end: ["b"] })), - TypeError, - "end key is not in the keyspace defined by prefix", - ); -}); - -dbTest("list prefix with empty prefix", async (db) => { - const res = await db.set(["a"], 1); - const entries = await collect(db.list({ prefix: [] })); - assertEquals(entries, [ - { key: ["a"], value: 1, versionstamp: res.versionstamp }, - ]); -}); - -dbTest("list prefix reverse", async (db) => { - const versionstamp = await setupData(db); - const entries = await collect(db.list({ prefix: ["a"] }, { reverse: true })); - assertEquals(entries, [ - { key: ["a", "e"], value: 4, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - { key: ["a", "a"], value: 0, versionstamp }, - ]); -}); - -dbTest("list prefix reverse with start", async (db) => { - const versionstamp = await setupData(db); - const entries = await collect( - db.list({ prefix: ["a"], start: ["a", "c"] }, { reverse: true }), - ); - assertEquals(entries, [ - { key: ["a", "e"], value: 4, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - ]); -}); - -dbTest("list prefix reverse with start empty", async (db) => { - await setupData(db); - const entries = await collect( - db.list({ prefix: ["a"], start: ["a", "f"] }, { reverse: true }), - ); - assertEquals(entries.length, 0); -}); - -dbTest("list prefix reverse with end", async (db) => { - const versionstamp = await setupData(db); - const entries = await collect( - db.list({ prefix: ["a"], end: ["a", "c"] }, { reverse: true }), - ); - assertEquals(entries, [ - { key: ["a", "b"], value: 1, versionstamp }, - { key: ["a", "a"], value: 0, versionstamp }, - ]); -}); - -dbTest("list prefix reverse with end empty", async (db) => { - await setupData(db); - const entries = await collect( - db.list({ prefix: ["a"], end: ["a", "a"] }, { reverse: true }), - ); - assertEquals(entries.length, 0); -}); - -dbTest("list prefix limit", async (db) => { - const versionstamp = await setupData(db); - const entries = await collect(db.list({ prefix: ["a"] }, { limit: 2 })); - assertEquals(entries, [ - { key: ["a", "a"], value: 0, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - ]); -}); - -dbTest("list prefix limit reverse", async (db) => { - const versionstamp = await setupData(db); - const entries = await collect( - db.list({ prefix: ["a"] }, { limit: 2, reverse: true }), - ); - assertEquals(entries, [ - { key: ["a", "e"], value: 4, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - ]); -}); - -dbTest("list prefix with small batch size", async (db) => { - const versionstamp = await setupData(db); - const entries = await collect(db.list({ prefix: ["a"] }, { batchSize: 2 })); - assertEquals(entries, [ - { key: ["a", "a"], value: 0, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - { key: ["a", "e"], value: 4, versionstamp }, - ]); -}); - -dbTest("list prefix with small batch size reverse", async (db) => { - const versionstamp = await setupData(db); - const entries = await collect( - db.list({ prefix: ["a"] }, { batchSize: 2, reverse: true }), - ); - assertEquals(entries, [ - { key: ["a", "e"], value: 4, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - { key: ["a", "a"], value: 0, versionstamp }, - ]); -}); - -dbTest("list prefix with small batch size and limit", async (db) => { - const versionstamp = await setupData(db); - const entries = await collect( - db.list({ prefix: ["a"] }, { batchSize: 2, limit: 3 }), - ); - assertEquals(entries, [ - { key: ["a", "a"], value: 0, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - ]); -}); - -dbTest("list prefix with small batch size and limit reverse", async (db) => { - const versionstamp = await setupData(db); - const entries = await collect( - db.list({ prefix: ["a"] }, { batchSize: 2, limit: 3, reverse: true }), - ); - assertEquals(entries, [ - { key: ["a", "e"], value: 4, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - ]); -}); - -dbTest("list prefix with manual cursor", async (db) => { - const versionstamp = await setupData(db); - const iterator = db.list({ prefix: ["a"] }, { limit: 2 }); - const values = await collect(iterator); - assertEquals(values, [ - { key: ["a", "a"], value: 0, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - ]); - - const cursor = iterator.cursor; - assertEquals(cursor, "AmIA"); - - const iterator2 = db.list({ prefix: ["a"] }, { cursor }); - const values2 = await collect(iterator2); - assertEquals(values2, [ - { key: ["a", "c"], value: 2, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - { key: ["a", "e"], value: 4, versionstamp }, - ]); -}); - -dbTest("list prefix with manual cursor reverse", async (db) => { - const versionstamp = await setupData(db); - - const iterator = db.list({ prefix: ["a"] }, { limit: 2, reverse: true }); - const values = await collect(iterator); - assertEquals(values, [ - { key: ["a", "e"], value: 4, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - ]); - - const cursor = iterator.cursor; - assertEquals(cursor, "AmQA"); - - const iterator2 = db.list({ prefix: ["a"] }, { cursor, reverse: true }); - const values2 = await collect(iterator2); - assertEquals(values2, [ - { key: ["a", "c"], value: 2, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - { key: ["a", "a"], value: 0, versionstamp }, - ]); -}); - -dbTest("list range", async (db) => { - const versionstamp = await setupData(db); - - const entries = await collect( - db.list({ start: ["a", "a"], end: ["a", "z"] }), - ); - assertEquals(entries, [ - { key: ["a", "a"], value: 0, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - { key: ["a", "e"], value: 4, versionstamp }, - ]); -}); - -dbTest("list range reverse", async (db) => { - const versionstamp = await setupData(db); - - const entries = await collect( - db.list({ start: ["a", "a"], end: ["a", "z"] }, { reverse: true }), - ); - assertEquals(entries, [ - { key: ["a", "e"], value: 4, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - { key: ["a", "a"], value: 0, versionstamp }, - ]); -}); - -dbTest("list range with limit", async (db) => { - const versionstamp = await setupData(db); - - const entries = await collect( - db.list({ start: ["a", "a"], end: ["a", "z"] }, { limit: 3 }), - ); - assertEquals(entries, [ - { key: ["a", "a"], value: 0, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - ]); -}); - -dbTest("list range with limit reverse", async (db) => { - const versionstamp = await setupData(db); - - const entries = await collect( - db.list({ start: ["a", "a"], end: ["a", "z"] }, { - limit: 3, - reverse: true, - }), - ); - assertEquals(entries, [ - { key: ["a", "e"], value: 4, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - ]); -}); - -dbTest("list range nesting", async (db) => { - const versionstamp = await setupData(db); - - const entries = await collect(db.list({ start: ["a"], end: ["a", "d"] })); - assertEquals(entries, [ - { key: ["a"], value: -1, versionstamp }, - { key: ["a", "a"], value: 0, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - ]); -}); - -dbTest("list range short", async (db) => { - const versionstamp = await setupData(db); - - const entries = await collect( - db.list({ start: ["a", "b"], end: ["a", "d"] }), - ); - assertEquals(entries, [ - { key: ["a", "b"], value: 1, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - ]); -}); - -dbTest("list range with manual cursor", async (db) => { - const versionstamp = await setupData(db); - - const iterator = db.list({ start: ["a", "b"], end: ["a", "z"] }, { - limit: 2, - }); - const entries = await collect(iterator); - assertEquals(entries, [ - { key: ["a", "b"], value: 1, versionstamp }, - { key: ["a", "c"], value: 2, versionstamp }, - ]); - - const cursor = iterator.cursor; - const iterator2 = db.list({ start: ["a", "b"], end: ["a", "z"] }, { - cursor, - }); - const entries2 = await collect(iterator2); - assertEquals(entries2, [ - { key: ["a", "d"], value: 3, versionstamp }, - { key: ["a", "e"], value: 4, versionstamp }, - ]); -}); - -dbTest("list range with manual cursor reverse", async (db) => { - const versionstamp = await setupData(db); - - const iterator = db.list({ start: ["a", "b"], end: ["a", "z"] }, { - limit: 2, - reverse: true, - }); - const entries = await collect(iterator); - assertEquals(entries, [ - { key: ["a", "e"], value: 4, versionstamp }, - { key: ["a", "d"], value: 3, versionstamp }, - ]); - - const cursor = iterator.cursor; - const iterator2 = db.list({ start: ["a", "b"], end: ["a", "z"] }, { - cursor, - reverse: true, - }); - const entries2 = await collect(iterator2); - assertEquals(entries2, [ - { key: ["a", "c"], value: 2, versionstamp }, - { key: ["a", "b"], value: 1, versionstamp }, - ]); -}); - -dbTest("list range with start greater than end", async (db) => { - await setupData(db); - await assertRejects( - async () => await collect(db.list({ start: ["b"], end: ["a"] })), - TypeError, - "start key is greater than end key", - ); -}); - -dbTest("list range with start equal to end", async (db) => { - await setupData(db); - const entries = await collect(db.list({ start: ["a"], end: ["a"] })); - assertEquals(entries.length, 0); -}); - -dbTest("list invalid selector", async (db) => { - await setupData(db); - - await assertRejects(async () => { - await collect( - db.list({ prefix: ["a"], start: ["a", "b"], end: ["a", "c"] }), - ); - }, TypeError); - - await assertRejects(async () => { - await collect( - // @ts-expect-error missing end - db.list({ start: ["a", "b"] }), - ); - }, TypeError); - - await assertRejects(async () => { - await collect( - // @ts-expect-error missing start - db.list({ end: ["a", "b"] }), - ); - }, TypeError); -}); - -dbTest("invalid versionstamp in atomic check rejects", async (db) => { - await assertRejects(async () => { - await db.atomic().check({ key: ["a"], versionstamp: "" }).commit(); - }, TypeError); - - await assertRejects(async () => { - await db.atomic().check({ key: ["a"], versionstamp: "xx".repeat(10) }) - .commit(); - }, TypeError); - - await assertRejects(async () => { - await db.atomic().check({ key: ["a"], versionstamp: "aa".repeat(11) }) - .commit(); - }, TypeError); -}); - -dbTest("invalid mutation type rejects", async (db) => { - await assertRejects(async () => { - await db.atomic() - // @ts-expect-error invalid type + value combo - .mutate({ key: ["a"], type: "set" }) - .commit(); - }, TypeError); - - await assertRejects(async () => { - await db.atomic() - // @ts-expect-error invalid type + value combo - .mutate({ key: ["a"], type: "delete", value: "123" }) - .commit(); - }, TypeError); - - await assertRejects(async () => { - await db.atomic() - // @ts-expect-error invalid type - .mutate({ key: ["a"], type: "foobar" }) - .commit(); - }, TypeError); - - await assertRejects(async () => { - await db.atomic() - // @ts-expect-error invalid type - .mutate({ key: ["a"], type: "foobar", value: "123" }) - .commit(); - }, TypeError); -}); - -dbTest("key ordering", async (db) => { - await db.atomic() - .set([new Uint8Array(0x1)], 0) - .set(["a"], 0) - .set([1n], 0) - .set([3.14], 0) - .set([false], 0) - .set([true], 0) - .commit(); - - assertEquals((await collect(db.list({ prefix: [] }))).map((x) => x.key), [ - [new Uint8Array(0x1)], - ["a"], - [1n], - [3.14], - [false], - [true], - ]); -}); - -dbTest("key size limit", async (db) => { - // 1 byte prefix + 1 byte suffix + 2045 bytes key - const lastValidKey = new Uint8Array(2046).fill(1); - const firstInvalidKey = new Uint8Array(2047).fill(1); - - const res = await db.set([lastValidKey], 1); - - assertEquals(await db.get([lastValidKey]), { - key: [lastValidKey], - value: 1, - versionstamp: res.versionstamp, - }); - - await assertRejects( - async () => await db.set([firstInvalidKey], 1), - TypeError, - "key too large for write (max 2048 bytes)", - ); - - await assertRejects( - async () => await db.get([firstInvalidKey]), - TypeError, - "key too large for read (max 2049 bytes)", - ); -}); - -dbTest("value size limit", async (db) => { - const lastValidValue = new Uint8Array(65536); - const firstInvalidValue = new Uint8Array(65537); - - const res = await db.set(["a"], lastValidValue); - assertEquals(await db.get(["a"]), { - key: ["a"], - value: lastValidValue, - versionstamp: res.versionstamp, - }); - - await assertRejects( - async () => await db.set(["b"], firstInvalidValue), - TypeError, - "value too large (max 65536 bytes)", - ); -}); - -dbTest("operation size limit", async (db) => { - const lastValidKeys: Deno.KvKey[] = new Array(10).fill(0).map(( - _, - i, - ) => ["a", i]); - const firstInvalidKeys: Deno.KvKey[] = new Array(11).fill(0).map(( - _, - i, - ) => ["a", i]); - const invalidCheckKeys: Deno.KvKey[] = new Array(101).fill(0).map(( - _, - i, - ) => ["a", i]); - - const res = await db.getMany(lastValidKeys); - assertEquals(res.length, 10); - - await assertRejects( - async () => await db.getMany(firstInvalidKeys), - TypeError, - "too many ranges (max 10)", - ); - - const res2 = await collect(db.list({ prefix: ["a"] }, { batchSize: 1000 })); - assertEquals(res2.length, 0); - - await assertRejects( - async () => await collect(db.list({ prefix: ["a"] }, { batchSize: 1001 })), - TypeError, - "too many entries (max 1000)", - ); - - // when batchSize is not specified, limit is used but is clamped to 500 - assertEquals( - (await collect(db.list({ prefix: ["a"] }, { limit: 1001 }))).length, - 0, - ); - - const res3 = await db.atomic() - .check(...lastValidKeys.map((key) => ({ - key, - versionstamp: null, - }))) - .mutate(...lastValidKeys.map((key) => ({ - key, - type: "set", - value: 1, - } satisfies Deno.KvMutation))) - .commit(); - assert(res3); - - await assertRejects( - async () => { - await db.atomic() - .check(...invalidCheckKeys.map((key) => ({ - key, - versionstamp: null, - }))) - .mutate(...lastValidKeys.map((key) => ({ - key, - type: "set", - value: 1, - } satisfies Deno.KvMutation))) - .commit(); - }, - TypeError, - "too many checks (max 100)", - ); - - const validMutateKeys: Deno.KvKey[] = new Array(1000).fill(0).map(( - _, - i, - ) => ["a", i]); - const invalidMutateKeys: Deno.KvKey[] = new Array(1001).fill(0).map(( - _, - i, - ) => ["a", i]); - - const res4 = await db.atomic() - .check(...lastValidKeys.map((key) => ({ - key, - versionstamp: null, - }))) - .mutate(...validMutateKeys.map((key) => ({ - key, - type: "set", - value: 1, - } satisfies Deno.KvMutation))) - .commit(); - assert(res4); - - await assertRejects( - async () => { - await db.atomic() - .check(...lastValidKeys.map((key) => ({ - key, - versionstamp: null, - }))) - .mutate(...invalidMutateKeys.map((key) => ({ - key, - type: "set", - value: 1, - } satisfies Deno.KvMutation))) - .commit(); - }, - TypeError, - "too many mutations (max 1000)", - ); -}); - -dbTest("total mutation size limit", async (db) => { - const keys: Deno.KvKey[] = new Array(1000).fill(0).map(( - _, - i, - ) => ["a", i]); - - const atomic = db.atomic(); - for (const key of keys) { - atomic.set(key, "foo"); - } - const res = await atomic.commit(); - assert(res); - - // Use bigger values to trigger "total mutation size too large" error - await assertRejects( - async () => { - const value = new Array(3000).fill("a").join(""); - const atomic = db.atomic(); - for (const key of keys) { - atomic.set(key, value); - } - await atomic.commit(); - }, - TypeError, - "total mutation size too large (max 819200 bytes)", - ); -}); - -dbTest("total key size limit", async (db) => { - const longString = new Array(1100).fill("a").join(""); - const keys: Deno.KvKey[] = new Array(80).fill(0).map(() => [longString]); - - const atomic = db.atomic(); - for (const key of keys) { - atomic.set(key, "foo"); - } - await assertRejects( - () => atomic.commit(), - TypeError, - "total key size too large (max 81920 bytes)", - ); -}); - -dbTest("keys must be arrays", async (db) => { - await assertRejects( - // @ts-expect-error invalid type - async () => await db.get("a"), - TypeError, - ); - - await assertRejects( - // @ts-expect-error invalid type - async () => await db.getMany(["a"]), - TypeError, - ); - - await assertRejects( - // @ts-expect-error invalid type - async () => await db.set("a", 1), - TypeError, - ); - - await assertRejects( - // @ts-expect-error invalid type - async () => await db.delete("a"), - TypeError, - ); - - await assertRejects( - async () => - await db.atomic() - // @ts-expect-error invalid type - .mutate({ key: "a", type: "set", value: 1 } satisfies Deno.KvMutation) - .commit(), - TypeError, - ); - - await assertRejects( - async () => - await db.atomic() - // @ts-expect-error invalid type - .check({ key: "a", versionstamp: null }) - .set(["a"], 1) - .commit(), - TypeError, - ); -}); - -Deno.test("Deno.Kv constructor throws", () => { - assertThrows(() => { - new Deno.Kv(); - }); -}); - -// This function is never called, it is just used to check that all the types -// are behaving as expected. -async function _typeCheckingTests() { - const kv = new Deno.Kv(); - - const a = await kv.get(["a"]); - assertType<IsExact<typeof a, Deno.KvEntryMaybe<unknown>>>(true); - - const b = await kv.get<string>(["b"]); - assertType<IsExact<typeof b, Deno.KvEntryMaybe<string>>>(true); - - const c = await kv.getMany([["a"], ["b"]]); - assertType< - IsExact<typeof c, [Deno.KvEntryMaybe<unknown>, Deno.KvEntryMaybe<unknown>]> - >(true); - - const d = await kv.getMany([["a"], ["b"]] as const); - assertType< - IsExact<typeof d, [Deno.KvEntryMaybe<unknown>, Deno.KvEntryMaybe<unknown>]> - >(true); - - const e = await kv.getMany<[string, number]>([["a"], ["b"]]); - assertType< - IsExact<typeof e, [Deno.KvEntryMaybe<string>, Deno.KvEntryMaybe<number>]> - >(true); - - const keys: Deno.KvKey[] = [["a"], ["b"]]; - const f = await kv.getMany(keys); - assertType<IsExact<typeof f, Deno.KvEntryMaybe<unknown>[]>>(true); - - const g = kv.list({ prefix: ["a"] }); - assertType<IsExact<typeof g, Deno.KvListIterator<unknown>>>(true); - const h = await g.next(); - assert(!h.done); - assertType<IsExact<typeof h.value, Deno.KvEntry<unknown>>>(true); - - const i = kv.list<string>({ prefix: ["a"] }); - assertType<IsExact<typeof i, Deno.KvListIterator<string>>>(true); - const j = await i.next(); - assert(!j.done); - assertType<IsExact<typeof j.value, Deno.KvEntry<string>>>(true); -} - -queueTest("basic listenQueue and enqueue", async (db) => { - const { promise, resolve } = Promise.withResolvers<void>(); - let dequeuedMessage: unknown = null; - const listener = db.listenQueue((msg) => { - dequeuedMessage = msg; - resolve(); - }); - try { - const res = await db.enqueue("test"); - assert(res.ok); - assertNotEquals(res.versionstamp, null); - await promise; - assertEquals(dequeuedMessage, "test"); - } finally { - db.close(); - await listener; - } -}); - -for (const { name, value } of VALUE_CASES) { - queueTest(`listenQueue and enqueue ${name}`, async (db) => { - const numEnqueues = 10; - let count = 0; - const deferreds: ReturnType<typeof Promise.withResolvers<unknown>>[] = []; - const listeners: Promise<void>[] = []; - listeners.push(db.listenQueue((msg: unknown) => { - deferreds[count++].resolve(msg); - })); - try { - for (let i = 0; i < numEnqueues; i++) { - deferreds.push(Promise.withResolvers<unknown>()); - await db.enqueue(value); - } - const dequeuedMessages = await Promise.all( - deferreds.map(({ promise }) => promise), - ); - for (let i = 0; i < numEnqueues; i++) { - assertEquals(dequeuedMessages[i], value); - } - } finally { - db.close(); - for (const listener of listeners) { - await listener; - } - } - }); -} - -queueTest("queue mixed types", async (db) => { - let deferred: ReturnType<typeof Promise.withResolvers<void>>; - let dequeuedMessage: unknown = null; - const listener = db.listenQueue((msg: unknown) => { - dequeuedMessage = msg; - deferred.resolve(); - }); - try { - for (const item of VALUE_CASES) { - deferred = Promise.withResolvers<void>(); - await db.enqueue(item.value); - await deferred.promise; - assertEquals(dequeuedMessage, item.value); - } - } finally { - db.close(); - await listener; - } -}); - -queueTest("queue delay", async (db) => { - let dequeueTime: number | undefined; - const { promise, resolve } = Promise.withResolvers<void>(); - let dequeuedMessage: unknown = null; - const listener = db.listenQueue((msg) => { - dequeueTime = Date.now(); - dequeuedMessage = msg; - resolve(); - }); - try { - const enqueueTime = Date.now(); - await db.enqueue("test", { delay: 1000 }); - await promise; - assertEquals(dequeuedMessage, "test"); - assert(dequeueTime !== undefined); - assert(dequeueTime - enqueueTime >= 1000); - } finally { - db.close(); - await listener; - } -}); - -queueTest("queue delay with atomic", async (db) => { - let dequeueTime: number | undefined; - const { promise, resolve } = Promise.withResolvers<void>(); - let dequeuedMessage: unknown = null; - const listener = db.listenQueue((msg) => { - dequeueTime = Date.now(); - dequeuedMessage = msg; - resolve(); - }); - try { - const enqueueTime = Date.now(); - const res = await db.atomic() - .enqueue("test", { delay: 1000 }) - .commit(); - assert(res.ok); - - await promise; - assertEquals(dequeuedMessage, "test"); - assert(dequeueTime !== undefined); - assert(dequeueTime - enqueueTime >= 1000); - } finally { - db.close(); - await listener; - } -}); - -queueTest("queue delay and now", async (db) => { - let count = 0; - let dequeueTime: number | undefined; - const { promise, resolve } = Promise.withResolvers<void>(); - let dequeuedMessage: unknown = null; - const listener = db.listenQueue((msg) => { - count += 1; - if (count == 2) { - dequeueTime = Date.now(); - dequeuedMessage = msg; - resolve(); - } - }); - try { - const enqueueTime = Date.now(); - await db.enqueue("test-1000", { delay: 1000 }); - await db.enqueue("test"); - await promise; - assertEquals(dequeuedMessage, "test-1000"); - assert(dequeueTime !== undefined); - assert(dequeueTime - enqueueTime >= 1000); - } finally { - db.close(); - await listener; - } -}); - -dbTest("queue negative delay", async (db) => { - await assertRejects(async () => { - await db.enqueue("test", { delay: -100 }); - }, TypeError); -}); - -dbTest("queue nan delay", async (db) => { - await assertRejects(async () => { - await db.enqueue("test", { delay: Number.NaN }); - }, TypeError); -}); - -dbTest("queue large delay", async (db) => { - await db.enqueue("test", { delay: 30 * 24 * 60 * 60 * 1000 }); - await assertRejects(async () => { - await db.enqueue("test", { delay: 30 * 24 * 60 * 60 * 1000 + 1 }); - }, TypeError); -}); - -queueTest("listenQueue with async callback", async (db) => { - const { promise, resolve } = Promise.withResolvers<void>(); - let dequeuedMessage: unknown = null; - const listener = db.listenQueue(async (msg) => { - dequeuedMessage = msg; - await sleep(100); - resolve(); - }); - try { - await db.enqueue("test"); - await promise; - assertEquals(dequeuedMessage, "test"); - } finally { - db.close(); - await listener; - } -}); - -queueTest("queue retries", async (db) => { - let count = 0; - const listener = db.listenQueue(async (_msg) => { - count += 1; - await sleep(10); - throw new TypeError("dequeue error"); - }); - try { - await db.enqueue("test"); - await sleep(10000); - } finally { - db.close(); - await listener; - } - - // There should have been 1 attempt + 3 retries in the 10 seconds - assertEquals(4, count); -}); - -queueTest("queue retries with backoffSchedule", async (db) => { - let count = 0; - const listener = db.listenQueue((_msg) => { - count += 1; - throw new TypeError("dequeue error"); - }); - try { - await db.enqueue("test", { backoffSchedule: [1] }); - await sleep(2000); - } finally { - db.close(); - await listener; - } - - // There should have been 1 attempt + 1 retry - assertEquals(2, count); -}); - -queueTest("multiple listenQueues", async (db) => { - const numListens = 10; - let count = 0; - const deferreds: ReturnType<typeof Promise.withResolvers<void>>[] = []; - const dequeuedMessages: unknown[] = []; - const listeners: Promise<void>[] = []; - for (let i = 0; i < numListens; i++) { - listeners.push(db.listenQueue((msg) => { - dequeuedMessages.push(msg); - deferreds[count++].resolve(); - })); - } - try { - for (let i = 0; i < numListens; i++) { - deferreds.push(Promise.withResolvers<void>()); - await db.enqueue("msg_" + i); - await deferreds[i].promise; - const msg = dequeuedMessages[i]; - assertEquals("msg_" + i, msg); - } - } finally { - db.close(); - for (let i = 0; i < numListens; i++) { - await listeners[i]; - } - } -}); - -queueTest("enqueue with atomic", async (db) => { - const { promise, resolve } = Promise.withResolvers<void>(); - let dequeuedMessage: unknown = null; - const listener = db.listenQueue((msg) => { - dequeuedMessage = msg; - resolve(); - }); - - try { - await db.set(["t"], "1"); - - let currentValue = await db.get(["t"]); - assertEquals("1", currentValue.value); - - const res = await db.atomic() - .check(currentValue) - .set(currentValue.key, "2") - .enqueue("test") - .commit(); - assert(res.ok); - - await promise; - assertEquals("test", dequeuedMessage); - - currentValue = await db.get(["t"]); - assertEquals("2", currentValue.value); - } finally { - db.close(); - await listener; - } -}); - -queueTest("enqueue with atomic nonce", async (db) => { - const { promise, resolve } = Promise.withResolvers<void>(); - let dequeuedMessage: unknown = null; - - const nonce = crypto.randomUUID(); - - const listener = db.listenQueue(async (val) => { - const message = val as { msg: string; nonce: string }; - const nonce = message.nonce; - const nonceValue = await db.get(["nonces", nonce]); - if (nonceValue.versionstamp === null) { - dequeuedMessage = message.msg; - resolve(); - return; - } - - assertNotEquals(nonceValue.versionstamp, null); - const res = await db.atomic() - .check(nonceValue) - .delete(["nonces", nonce]) - .set(["a", "b"], message.msg) - .commit(); - if (res.ok) { - // Simulate an error so that the message has to be redelivered - throw new Error("injected error"); - } - }); - - try { - const res = await db.atomic() - .check({ key: ["nonces", nonce], versionstamp: null }) - .set(["nonces", nonce], true) - .enqueue({ msg: "test", nonce }) - .commit(); - assert(res.ok); - - await promise; - assertEquals("test", dequeuedMessage); - - const currentValue = await db.get(["a", "b"]); - assertEquals("test", currentValue.value); - - const nonceValue = await db.get(["nonces", nonce]); - assertEquals(nonceValue.versionstamp, null); - } finally { - db.close(); - await listener; - } -}); - -Deno.test({ - name: "queue persistence with inflight messages", - sanitizeOps: false, - sanitizeResources: false, - async fn() { - const filename = await Deno.makeTempFile({ prefix: "queue_db" }); - try { - let db: Deno.Kv = await Deno.openKv(filename); - - let count = 0; - let deferred = Promise.withResolvers<void>(); - - // Register long-running handler. - let listener = db.listenQueue(async (_msg) => { - count += 1; - if (count == 3) { - deferred.resolve(); - } - await new Promise(() => {}); - }); - - // Enqueue 3 messages. - await db.enqueue("msg0"); - await db.enqueue("msg1"); - await db.enqueue("msg2"); - await deferred.promise; - - // Close the database and wait for the listener to finish. - db.close(); - await listener; - - // Wait at least MESSAGE_DEADLINE_TIMEOUT before reopening the database. - // This ensures that inflight messages are requeued immediately after - // the database is reopened. - // https://github.com/denoland/denokv/blob/efb98a1357d37291a225ed5cf1fc4ecc7c737fab/sqlite/backend.rs#L120 - await sleep(6000); - - // Now reopen the database. - db = await Deno.openKv(filename); - - count = 0; - deferred = Promise.withResolvers<void>(); - - // Register a handler that will complete quickly. - listener = db.listenQueue((_msg) => { - count += 1; - if (count == 3) { - deferred.resolve(); - } - }); - - // Wait for the handlers to finish. - await deferred.promise; - assertEquals(3, count); - db.close(); - await listener; - } finally { - try { - await Deno.remove(filename); - } catch { - // pass - } - } - }, -}); - -Deno.test({ - name: "queue persistence with delay messages", - async fn() { - const filename = await Deno.makeTempFile({ prefix: "queue_db" }); - try { - await Deno.remove(filename); - } catch { - // pass - } - try { - let db: Deno.Kv = await Deno.openKv(filename); - - let count = 0; - let deferred = Promise.withResolvers<void>(); - - // Register long-running handler. - let listener = db.listenQueue((_msg) => {}); - - // Enqueue 3 messages into the future. - await db.enqueue("msg0", { delay: 10000 }); - await db.enqueue("msg1", { delay: 10000 }); - await db.enqueue("msg2", { delay: 10000 }); - - // Close the database and wait for the listener to finish. - db.close(); - await listener; - - // Now reopen the database. - db = await Deno.openKv(filename); - - count = 0; - deferred = Promise.withResolvers<void>(); - - // Register a handler that will complete quickly. - listener = db.listenQueue((_msg) => { - count += 1; - if (count == 3) { - deferred.resolve(); - } - }); - - // Wait for the handlers to finish. - await deferred.promise; - assertEquals(3, count); - db.close(); - await listener; - } finally { - try { - await Deno.remove(filename); - } catch { - // pass - } - } - }, -}); - -Deno.test({ - name: "different kv instances for enqueue and queueListen", - async fn() { - const filename = await Deno.makeTempFile({ prefix: "queue_db" }); - try { - const db0 = await Deno.openKv(filename); - const db1 = await Deno.openKv(filename); - const { promise, resolve } = Promise.withResolvers<void>(); - let dequeuedMessage: unknown = null; - const listener = db0.listenQueue((msg) => { - dequeuedMessage = msg; - resolve(); - }); - try { - const res = await db1.enqueue("test"); - assert(res.ok); - assertNotEquals(res.versionstamp, null); - await promise; - assertEquals(dequeuedMessage, "test"); - } finally { - db0.close(); - await listener; - db1.close(); - } - } finally { - try { - await Deno.remove(filename); - } catch { - // pass - } - } - }, -}); - -Deno.test({ - name: "queue graceful close", - async fn() { - const db: Deno.Kv = await Deno.openKv(":memory:"); - const listener = db.listenQueue((_msg) => {}); - db.close(); - await listener; - }, -}); - -dbTest("invalid backoffSchedule", async (db) => { - await assertRejects( - async () => { - await db.enqueue("foo", { backoffSchedule: [1, 1, 1, 1, 1, 1] }); - }, - TypeError, - "invalid backoffSchedule", - ); - await assertRejects( - async () => { - await db.enqueue("foo", { backoffSchedule: [3600001] }); - }, - TypeError, - "invalid backoffSchedule", - ); -}); - -dbTest("atomic operation is exposed", (db) => { - assert(Deno.AtomicOperation); - const ao = db.atomic(); - assert(ao instanceof Deno.AtomicOperation); -}); - -Deno.test({ - name: "racy open", - async fn() { - for (let i = 0; i < 100; i++) { - const filename = await Deno.makeTempFile({ prefix: "racy_open_db" }); - try { - const [db1, db2, db3] = await Promise.all([ - Deno.openKv(filename), - Deno.openKv(filename), - Deno.openKv(filename), - ]); - db1.close(); - db2.close(); - db3.close(); - } finally { - await Deno.remove(filename); - } - } - }, -}); - -Deno.test({ - name: "racy write", - async fn() { - const filename = await Deno.makeTempFile({ prefix: "racy_write_db" }); - const concurrency = 20; - const iterations = 5; - try { - const dbs = await Promise.all( - Array(concurrency).fill(0).map(() => Deno.openKv(filename)), - ); - try { - for (let i = 0; i < iterations; i++) { - await Promise.all( - dbs.map((db) => db.atomic().sum(["counter"], 1n).commit()), - ); - } - assertEquals( - ((await dbs[0].get(["counter"])).value as Deno.KvU64).value, - BigInt(concurrency * iterations), - ); - } finally { - dbs.forEach((db) => db.close()); - } - } finally { - await Deno.remove(filename); - } - }, -}); - -Deno.test({ - name: "kv expiration", - async fn() { - const filename = await Deno.makeTempFile({ prefix: "kv_expiration_db" }); - try { - await Deno.remove(filename); - } catch { - // pass - } - let db: Deno.Kv | null = null; - - try { - db = await Deno.openKv(filename); - - await db.set(["a"], 1, { expireIn: 1000 }); - await db.set(["b"], 2, { expireIn: 1000 }); - assertEquals((await db.get(["a"])).value, 1); - assertEquals((await db.get(["b"])).value, 2); - - // Value overwrite should also reset expiration - await db.set(["b"], 2, { expireIn: 3600 * 1000 }); - - // Wait for expiration - await sleep(1000); - - // Re-open to trigger immediate cleanup - db.close(); - db = null; - db = await Deno.openKv(filename); - - let ok = false; - for (let i = 0; i < 50; i++) { - await sleep(100); - if ( - JSON.stringify( - (await db.getMany([["a"], ["b"]])).map((x) => x.value), - ) === "[null,2]" - ) { - ok = true; - break; - } - } - - if (!ok) { - throw new Error("Values did not expire"); - } - } finally { - if (db) { - try { - db.close(); - } catch { - // pass - } - } - try { - await Deno.remove(filename); - } catch { - // pass - } - } - }, -}); - -Deno.test({ - name: "kv expiration with atomic", - async fn() { - const filename = await Deno.makeTempFile({ prefix: "kv_expiration_db" }); - try { - await Deno.remove(filename); - } catch { - // pass - } - let db: Deno.Kv | null = null; - - try { - db = await Deno.openKv(filename); - - await db.atomic().set(["a"], 1, { expireIn: 1000 }).set(["b"], 2, { - expireIn: 1000, - }).commit(); - assertEquals((await db.getMany([["a"], ["b"]])).map((x) => x.value), [ - 1, - 2, - ]); - - // Wait for expiration - await sleep(1000); - - // Re-open to trigger immediate cleanup - db.close(); - db = null; - db = await Deno.openKv(filename); - - let ok = false; - for (let i = 0; i < 50; i++) { - await sleep(100); - if ( - JSON.stringify( - (await db.getMany([["a"], ["b"]])).map((x) => x.value), - ) === "[null,null]" - ) { - ok = true; - break; - } - } - - if (!ok) { - throw new Error("Values did not expire"); - } - } finally { - if (db) { - try { - db.close(); - } catch { - // pass - } - } - try { - await Deno.remove(filename); - } catch { - // pass - } - } - }, -}); - -Deno.test({ - name: "remote backend", - async fn() { - const db = await Deno.openKv("http://localhost:4545/kv_remote_authorize"); - try { - await db.set(["some-key"], 1); - const entry = await db.get(["some-key"]); - assertEquals(entry.value, null); - assertEquals(entry.versionstamp, null); - } finally { - db.close(); - } - }, -}); - -Deno.test({ - name: "remote backend invalid format", - async fn() { - const db = await Deno.openKv( - "http://localhost:4545/kv_remote_authorize_invalid_format", - ); - - await assertRejects( - async () => { - await db.set(["some-key"], 1); - }, - Error, - "Failed to parse metadata: ", - ); - - db.close(); - }, -}); - -Deno.test({ - name: "remote backend invalid version", - async fn() { - const db = await Deno.openKv( - "http://localhost:4545/kv_remote_authorize_invalid_version", - ); - - await assertRejects( - async () => { - await db.set(["some-key"], 1); - }, - Error, - "Failed to parse metadata: unsupported metadata version: 1000", - ); - - db.close(); - }, -}); - -Deno.test( - { permissions: { read: true } }, - async function kvExplicitResourceManagement() { - let kv2: Deno.Kv; - - { - using kv = await Deno.openKv(":memory:"); - kv2 = kv; - - const res = await kv.get(["a"]); - assertEquals(res.versionstamp, null); - } - - await assertRejects(() => kv2.get(["a"]), Deno.errors.BadResource); - }, -); - -Deno.test( - { permissions: { read: true } }, - async function kvExplicitResourceManagementManualClose() { - using kv = await Deno.openKv(":memory:"); - kv.close(); - await assertRejects(() => kv.get(["a"]), Deno.errors.BadResource); - // calling [Symbol.dispose] after manual close is a no-op - }, -); - -dbTest("key watch", async (db) => { - const changeHistory: Deno.KvEntryMaybe<number>[] = []; - const watcher: ReadableStream<Deno.KvEntryMaybe<number>[]> = db.watch< - number[] - >([["key"]]); - - const reader = watcher.getReader(); - const expectedChanges = 2; - - const work = (async () => { - for (let i = 0; i < expectedChanges; i++) { - const message = await reader.read(); - if (message.done) { - throw new Error("Unexpected end of stream"); - } - changeHistory.push(message.value[0]); - } - - await reader.cancel(); - })(); - - while (changeHistory.length !== 1) { - await sleep(100); - } - assertEquals(changeHistory[0], { - key: ["key"], - value: null, - versionstamp: null, - }); - - const { versionstamp } = await db.set(["key"], 1); - while (changeHistory.length as number !== 2) { - await sleep(100); - } - assertEquals(changeHistory[1], { - key: ["key"], - value: 1, - versionstamp, - }); - - await work; - await reader.cancel(); -}); - -dbTest("set with key versionstamp suffix", async (db) => { - const result1 = await Array.fromAsync(db.list({ prefix: ["a"] })); - assertEquals(result1, []); - - const setRes1 = await db.set(["a", db.commitVersionstamp()], "b"); - assert(setRes1.ok); - assert(setRes1.versionstamp > ZERO_VERSIONSTAMP); - - const result2 = await Array.fromAsync(db.list({ prefix: ["a"] })); - assertEquals(result2.length, 1); - assertEquals(result2[0].key[1], setRes1.versionstamp); - assertEquals(result2[0].value, "b"); - assertEquals(result2[0].versionstamp, setRes1.versionstamp); - - const setRes2 = await db.atomic().set(["a", db.commitVersionstamp()], "c") - .commit(); - assert(setRes2.ok); - assert(setRes2.versionstamp > setRes1.versionstamp); - - const result3 = await Array.fromAsync(db.list({ prefix: ["a"] })); - assertEquals(result3.length, 2); - assertEquals(result3[1].key[1], setRes2.versionstamp); - assertEquals(result3[1].value, "c"); - assertEquals(result3[1].versionstamp, setRes2.versionstamp); - - await assertRejects( - async () => await db.set(["a", db.commitVersionstamp(), "a"], "x"), - TypeError, - "expected string, number, bigint, ArrayBufferView, boolean", - ); -}); - -Deno.test({ - name: "watch should stop when db closed", - async fn() { - const db = await Deno.openKv(":memory:"); - - const watch = db.watch([["a"]]); - const completion = (async () => { - for await (const _item of watch) { - // pass - } - })(); - - setTimeout(() => { - db.close(); - }, 100); - - await completion; - }, -}); diff --git a/cli/tests/unit/link_test.ts b/cli/tests/unit/link_test.ts deleted file mode 100644 index 6048b8add..000000000 --- a/cli/tests/unit/link_test.ts +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertRejects, - assertThrows, -} from "./test_util.ts"; - -Deno.test( - { permissions: { read: true, write: true } }, - function linkSyncSuccess() { - const testDir = Deno.makeTempDirSync(); - const oldData = "Hardlink"; - const oldName = testDir + "/oldname"; - const newName = testDir + "/newname"; - Deno.writeFileSync(oldName, new TextEncoder().encode(oldData)); - // Create the hard link. - Deno.linkSync(oldName, newName); - // We should expect reading the same content. - const newData = Deno.readTextFileSync(newName); - assertEquals(oldData, newData); - // Writing to newname also affects oldname. - const newData2 = "Modified"; - Deno.writeFileSync(newName, new TextEncoder().encode(newData2)); - assertEquals( - newData2, - Deno.readTextFileSync(oldName), - ); - // Writing to oldname also affects newname. - const newData3 = "ModifiedAgain"; - Deno.writeFileSync(oldName, new TextEncoder().encode(newData3)); - assertEquals( - newData3, - Deno.readTextFileSync(newName), - ); - // Remove oldname. File still accessible through newname. - Deno.removeSync(oldName); - const newNameStat = Deno.statSync(newName); - assert(newNameStat.isFile); - assert(!newNameStat.isSymlink); // Not a symlink. - assertEquals( - newData3, - Deno.readTextFileSync(newName), - ); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function linkSyncExists() { - const testDir = Deno.makeTempDirSync(); - const oldName = testDir + "/oldname"; - const newName = testDir + "/newname"; - Deno.writeFileSync(oldName, new TextEncoder().encode("oldName")); - // newname is already created. - Deno.writeFileSync(newName, new TextEncoder().encode("newName")); - - assertThrows( - () => { - Deno.linkSync(oldName, newName); - }, - Deno.errors.AlreadyExists, - `link '${oldName}' -> '${newName}'`, - ); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function linkSyncNotFound() { - const testDir = Deno.makeTempDirSync(); - const oldName = testDir + "/oldname"; - const newName = testDir + "/newname"; - - assertThrows( - () => { - Deno.linkSync(oldName, newName); - }, - Deno.errors.NotFound, - `link '${oldName}' -> '${newName}'`, - ); - }, -); - -Deno.test( - { permissions: { read: false, write: true } }, - function linkSyncReadPerm() { - assertThrows(() => { - Deno.linkSync("oldbaddir", "newbaddir"); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, write: false } }, - function linkSyncWritePerm() { - assertThrows(() => { - Deno.linkSync("oldbaddir", "newbaddir"); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function linkSuccess() { - const testDir = Deno.makeTempDirSync(); - const oldData = "Hardlink"; - const oldName = testDir + "/oldname"; - const newName = testDir + "/newname"; - Deno.writeFileSync(oldName, new TextEncoder().encode(oldData)); - // Create the hard link. - await Deno.link(oldName, newName); - // We should expect reading the same content. - const newData = Deno.readTextFileSync(newName); - assertEquals(oldData, newData); - // Writing to newname also affects oldname. - const newData2 = "Modified"; - Deno.writeFileSync(newName, new TextEncoder().encode(newData2)); - assertEquals( - newData2, - Deno.readTextFileSync(oldName), - ); - // Writing to oldname also affects newname. - const newData3 = "ModifiedAgain"; - Deno.writeFileSync(oldName, new TextEncoder().encode(newData3)); - assertEquals( - newData3, - Deno.readTextFileSync(newName), - ); - // Remove oldname. File still accessible through newname. - Deno.removeSync(oldName); - const newNameStat = Deno.statSync(newName); - assert(newNameStat.isFile); - assert(!newNameStat.isSymlink); // Not a symlink. - assertEquals( - newData3, - Deno.readTextFileSync(newName), - ); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function linkExists() { - const testDir = Deno.makeTempDirSync(); - const oldName = testDir + "/oldname"; - const newName = testDir + "/newname"; - Deno.writeFileSync(oldName, new TextEncoder().encode("oldName")); - // newname is already created. - Deno.writeFileSync(newName, new TextEncoder().encode("newName")); - - await assertRejects( - async () => { - await Deno.link(oldName, newName); - }, - Deno.errors.AlreadyExists, - `link '${oldName}' -> '${newName}'`, - ); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function linkNotFound() { - const testDir = Deno.makeTempDirSync(); - const oldName = testDir + "/oldname"; - const newName = testDir + "/newname"; - - await assertRejects( - async () => { - await Deno.link(oldName, newName); - }, - Deno.errors.NotFound, - `link '${oldName}' -> '${newName}'`, - ); - }, -); - -Deno.test( - { permissions: { read: false, write: true } }, - async function linkReadPerm() { - await assertRejects(async () => { - await Deno.link("oldbaddir", "newbaddir"); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, write: false } }, - async function linkWritePerm() { - await assertRejects(async () => { - await Deno.link("oldbaddir", "newbaddir"); - }, Deno.errors.PermissionDenied); - }, -); diff --git a/cli/tests/unit/make_temp_test.ts b/cli/tests/unit/make_temp_test.ts deleted file mode 100644 index cbbae8dfe..000000000 --- a/cli/tests/unit/make_temp_test.ts +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertRejects, - assertThrows, -} from "./test_util.ts"; - -Deno.test({ permissions: { write: true } }, function makeTempDirSyncSuccess() { - const dir1 = Deno.makeTempDirSync({ prefix: "hello", suffix: "world" }); - const dir2 = Deno.makeTempDirSync({ prefix: "hello", suffix: "world" }); - // Check that both dirs are different. - assert(dir1 !== dir2); - for (const dir of [dir1, dir2]) { - // Check that the prefix and suffix are applied. - const lastPart = dir.replace(/^.*[\\\/]/, ""); - assert(lastPart.startsWith("hello")); - assert(lastPart.endsWith("world")); - } - // Check that the `dir` option works. - const dir3 = Deno.makeTempDirSync({ dir: dir1 }); - assert(dir3.startsWith(dir1)); - assert(/^[\\\/]/.test(dir3.slice(dir1.length))); - // Check that creating a temp dir inside a nonexisting directory fails. - assertThrows(() => { - Deno.makeTempDirSync({ dir: "/baddir" }); - }, Deno.errors.NotFound); -}); - -Deno.test( - { permissions: { read: true, write: true } }, - function makeTempDirSyncMode() { - const path = Deno.makeTempDirSync(); - const pathInfo = Deno.statSync(path); - if (Deno.build.os !== "windows") { - assertEquals(pathInfo.mode! & 0o777, 0o700 & ~Deno.umask()); - } - }, -); - -Deno.test({ permissions: { write: false } }, function makeTempDirSyncPerm() { - // makeTempDirSync should require write permissions (for now). - assertThrows(() => { - Deno.makeTempDirSync({ dir: "/baddir" }); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { permissions: { write: true } }, - async function makeTempDirSuccess() { - const dir1 = await Deno.makeTempDir({ prefix: "hello", suffix: "world" }); - const dir2 = await Deno.makeTempDir({ prefix: "hello", suffix: "world" }); - // Check that both dirs are different. - assert(dir1 !== dir2); - for (const dir of [dir1, dir2]) { - // Check that the prefix and suffix are applied. - const lastPart = dir.replace(/^.*[\\\/]/, ""); - assert(lastPart.startsWith("hello")); - assert(lastPart.endsWith("world")); - } - // Check that the `dir` option works. - const dir3 = await Deno.makeTempDir({ dir: dir1 }); - assert(dir3.startsWith(dir1)); - assert(/^[\\\/]/.test(dir3.slice(dir1.length))); - // Check that creating a temp dir inside a nonexisting directory fails. - await assertRejects(async () => { - await Deno.makeTempDir({ dir: "/baddir" }); - }, Deno.errors.NotFound); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function makeTempDirMode() { - const path = await Deno.makeTempDir(); - const pathInfo = Deno.statSync(path); - if (Deno.build.os !== "windows") { - assertEquals(pathInfo.mode! & 0o777, 0o700 & ~Deno.umask()); - } - }, -); - -Deno.test({ permissions: { write: true } }, function makeTempFileSyncSuccess() { - const file1 = Deno.makeTempFileSync({ prefix: "hello", suffix: "world" }); - const file2 = Deno.makeTempFileSync({ prefix: "hello", suffix: "world" }); - // Check that both dirs are different. - assert(file1 !== file2); - for (const dir of [file1, file2]) { - // Check that the prefix and suffix are applied. - const lastPart = dir.replace(/^.*[\\\/]/, ""); - assert(lastPart.startsWith("hello")); - assert(lastPart.endsWith("world")); - } - // Check that the `dir` option works. - const dir = Deno.makeTempDirSync({ prefix: "tempdir" }); - const file3 = Deno.makeTempFileSync({ dir }); - assert(file3.startsWith(dir)); - assert(/^[\\\/]/.test(file3.slice(dir.length))); - // Check that creating a temp file inside a nonexisting directory fails. - assertThrows(() => { - Deno.makeTempFileSync({ dir: "/baddir" }); - }, Deno.errors.NotFound); -}); - -Deno.test( - { permissions: { read: true, write: true } }, - function makeTempFileSyncMode() { - const path = Deno.makeTempFileSync(); - const pathInfo = Deno.statSync(path); - if (Deno.build.os !== "windows") { - assertEquals(pathInfo.mode! & 0o777, 0o600 & ~Deno.umask()); - } - }, -); - -Deno.test({ permissions: { write: false } }, function makeTempFileSyncPerm() { - // makeTempFileSync should require write permissions (for now). - assertThrows(() => { - Deno.makeTempFileSync({ dir: "/baddir" }); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { permissions: { write: true } }, - async function makeTempFileSuccess() { - const file1 = await Deno.makeTempFile({ prefix: "hello", suffix: "world" }); - const file2 = await Deno.makeTempFile({ prefix: "hello", suffix: "world" }); - // Check that both dirs are different. - assert(file1 !== file2); - for (const dir of [file1, file2]) { - // Check that the prefix and suffix are applied. - const lastPart = dir.replace(/^.*[\\\/]/, ""); - assert(lastPart.startsWith("hello")); - assert(lastPart.endsWith("world")); - } - // Check that the `dir` option works. - const dir = Deno.makeTempDirSync({ prefix: "tempdir" }); - const file3 = await Deno.makeTempFile({ dir }); - assert(file3.startsWith(dir)); - assert(/^[\\\/]/.test(file3.slice(dir.length))); - // Check that creating a temp file inside a nonexisting directory fails. - await assertRejects(async () => { - await Deno.makeTempFile({ dir: "/baddir" }); - }, Deno.errors.NotFound); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function makeTempFileMode() { - const path = await Deno.makeTempFile(); - const pathInfo = Deno.statSync(path); - if (Deno.build.os !== "windows") { - assertEquals(pathInfo.mode! & 0o777, 0o600 & ~Deno.umask()); - } - }, -); diff --git a/cli/tests/unit/message_channel_test.ts b/cli/tests/unit/message_channel_test.ts deleted file mode 100644 index 88fb1ba11..000000000 --- a/cli/tests/unit/message_channel_test.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -// NOTE: these are just sometests to test the TypeScript types. Real coverage is -// provided by WPT. -import { assert, assertEquals } from "@test_util/std/assert/mod.ts"; - -Deno.test("messagechannel", async () => { - const mc = new MessageChannel(); - const mc2 = new MessageChannel(); - assert(mc.port1); - assert(mc.port2); - - const { promise, resolve } = Promise.withResolvers<void>(); - - mc.port2.onmessage = (e) => { - assertEquals(e.data, "hello"); - assertEquals(e.ports.length, 1); - assert(e.ports[0] instanceof MessagePort); - e.ports[0].close(); - resolve(); - }; - - mc.port1.postMessage("hello", [mc2.port1]); - mc.port1.close(); - - await promise; - - mc.port2.close(); - mc2.port2.close(); -}); - -Deno.test("messagechannel clone port", async () => { - const mc = new MessageChannel(); - const mc2 = new MessageChannel(); - assert(mc.port1); - assert(mc.port2); - - const { promise, resolve } = Promise.withResolvers<void>(); - - mc.port2.onmessage = (e) => { - const { port } = e.data; - assertEquals(e.ports.length, 1); - assert(e.ports[0] instanceof MessagePort); - assertEquals(e.ports[0], port); - e.ports[0].close(); - resolve(); - }; - - mc.port1.postMessage({ port: mc2.port1 }, [mc2.port1]); - mc.port1.close(); - - await promise; - - mc.port2.close(); - mc2.port2.close(); -}); diff --git a/cli/tests/unit/mkdir_test.ts b/cli/tests/unit/mkdir_test.ts deleted file mode 100644 index 0948a1a84..000000000 --- a/cli/tests/unit/mkdir_test.ts +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertRejects, - assertThrows, - pathToAbsoluteFileUrl, -} from "./test_util.ts"; - -function assertDirectory(path: string, mode?: number) { - const info = Deno.lstatSync(path); - assert(info.isDirectory); - if (Deno.build.os !== "windows" && mode !== undefined) { - assertEquals(info.mode! & 0o777, mode & ~Deno.umask()); - } -} - -Deno.test( - { permissions: { read: true, write: true } }, - function mkdirSyncSuccess() { - const path = Deno.makeTempDirSync() + "/dir"; - Deno.mkdirSync(path); - assertDirectory(path); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function mkdirSyncMode() { - const path = Deno.makeTempDirSync() + "/dir"; - Deno.mkdirSync(path, { mode: 0o737 }); - assertDirectory(path, 0o737); - }, -); - -Deno.test({ permissions: { write: false } }, function mkdirSyncPerm() { - assertThrows(() => { - Deno.mkdirSync("/baddir"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { permissions: { read: true, write: true } }, - async function mkdirSuccess() { - const path = Deno.makeTempDirSync() + "/dir"; - await Deno.mkdir(path); - assertDirectory(path); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function mkdirMode() { - const path = Deno.makeTempDirSync() + "/dir"; - await Deno.mkdir(path, { mode: 0o737 }); - assertDirectory(path, 0o737); - }, -); - -Deno.test({ permissions: { write: true } }, function mkdirErrSyncIfExists() { - assertThrows( - () => { - Deno.mkdirSync("."); - }, - Deno.errors.AlreadyExists, - `mkdir '.'`, - ); -}); - -Deno.test({ permissions: { write: true } }, async function mkdirErrIfExists() { - await assertRejects( - async () => { - await Deno.mkdir("."); - }, - Deno.errors.AlreadyExists, - `mkdir '.'`, - ); -}); - -Deno.test( - { permissions: { read: true, write: true } }, - function mkdirSyncRecursive() { - const path = Deno.makeTempDirSync() + "/nested/directory"; - Deno.mkdirSync(path, { recursive: true }); - assertDirectory(path); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function mkdirRecursive() { - const path = Deno.makeTempDirSync() + "/nested/directory"; - await Deno.mkdir(path, { recursive: true }); - assertDirectory(path); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function mkdirSyncRecursiveMode() { - const nested = Deno.makeTempDirSync() + "/nested"; - const path = nested + "/dir"; - Deno.mkdirSync(path, { mode: 0o737, recursive: true }); - assertDirectory(path, 0o737); - assertDirectory(nested, 0o737); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function mkdirRecursiveMode() { - const nested = Deno.makeTempDirSync() + "/nested"; - const path = nested + "/dir"; - await Deno.mkdir(path, { mode: 0o737, recursive: true }); - assertDirectory(path, 0o737); - assertDirectory(nested, 0o737); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function mkdirSyncRecursiveIfExists() { - const path = Deno.makeTempDirSync() + "/dir"; - Deno.mkdirSync(path, { mode: 0o737 }); - Deno.mkdirSync(path, { recursive: true }); - Deno.mkdirSync(path, { recursive: true, mode: 0o731 }); - assertDirectory(path, 0o737); - if (Deno.build.os !== "windows") { - const pathLink = path + "Link"; - Deno.symlinkSync(path, pathLink); - Deno.mkdirSync(pathLink, { recursive: true }); - Deno.mkdirSync(pathLink, { recursive: true, mode: 0o731 }); - assertDirectory(path, 0o737); - } - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function mkdirRecursiveIfExists() { - const path = Deno.makeTempDirSync() + "/dir"; - await Deno.mkdir(path, { mode: 0o737 }); - await Deno.mkdir(path, { recursive: true }); - await Deno.mkdir(path, { recursive: true, mode: 0o731 }); - assertDirectory(path, 0o737); - if (Deno.build.os !== "windows") { - const pathLink = path + "Link"; - Deno.symlinkSync(path, pathLink); - await Deno.mkdir(pathLink, { recursive: true }); - await Deno.mkdir(pathLink, { recursive: true, mode: 0o731 }); - assertDirectory(path, 0o737); - } - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function mkdirSyncErrors() { - const testDir = Deno.makeTempDirSync(); - const emptydir = testDir + "/empty"; - const fulldir = testDir + "/dir"; - const file = fulldir + "/file"; - Deno.mkdirSync(emptydir); - Deno.mkdirSync(fulldir); - Deno.createSync(file).close(); - - assertThrows(() => { - Deno.mkdirSync(emptydir, { recursive: false }); - }, Deno.errors.AlreadyExists); - assertThrows(() => { - Deno.mkdirSync(fulldir, { recursive: false }); - }, Deno.errors.AlreadyExists); - assertThrows(() => { - Deno.mkdirSync(file, { recursive: false }); - }, Deno.errors.AlreadyExists); - assertThrows(() => { - Deno.mkdirSync(file, { recursive: true }); - }, Deno.errors.AlreadyExists); - - if (Deno.build.os !== "windows") { - const fileLink = testDir + "/fileLink"; - const dirLink = testDir + "/dirLink"; - const danglingLink = testDir + "/danglingLink"; - Deno.symlinkSync(file, fileLink); - Deno.symlinkSync(emptydir, dirLink); - Deno.symlinkSync(testDir + "/nonexistent", danglingLink); - - assertThrows(() => { - Deno.mkdirSync(dirLink, { recursive: false }); - }, Deno.errors.AlreadyExists); - assertThrows(() => { - Deno.mkdirSync(fileLink, { recursive: false }); - }, Deno.errors.AlreadyExists); - assertThrows(() => { - Deno.mkdirSync(fileLink, { recursive: true }); - }, Deno.errors.AlreadyExists); - assertThrows(() => { - Deno.mkdirSync(danglingLink, { recursive: false }); - }, Deno.errors.AlreadyExists); - assertThrows(() => { - Deno.mkdirSync(danglingLink, { recursive: true }); - }, Deno.errors.AlreadyExists); - } - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function mkdirSyncRelativeUrlPath() { - const testDir = Deno.makeTempDirSync(); - const nestedDir = testDir + "/nested"; - // Add trailing slash so base path is treated as a directory. pathToAbsoluteFileUrl removes trailing slashes. - const path = new URL("../dir", pathToAbsoluteFileUrl(nestedDir) + "/"); - - Deno.mkdirSync(nestedDir); - Deno.mkdirSync(path); - - assertDirectory(testDir + "/dir"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function mkdirRelativeUrlPath() { - const testDir = Deno.makeTempDirSync(); - const nestedDir = testDir + "/nested"; - // Add trailing slash so base path is treated as a directory. pathToAbsoluteFileUrl removes trailing slashes. - const path = new URL("../dir", pathToAbsoluteFileUrl(nestedDir) + "/"); - - await Deno.mkdir(nestedDir); - await Deno.mkdir(path); - - assertDirectory(testDir + "/dir"); - }, -); diff --git a/cli/tests/unit/navigator_test.ts b/cli/tests/unit/navigator_test.ts deleted file mode 100644 index 5dcc423fa..000000000 --- a/cli/tests/unit/navigator_test.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert } from "./test_util.ts"; - -Deno.test(function navigatorNumCpus() { - assert(navigator.hardwareConcurrency > 0); -}); - -Deno.test(function navigatorUserAgent() { - const pattern = /Deno\/\d+\.\d+\.\d+/; - assert(pattern.test(navigator.userAgent)); -}); diff --git a/cli/tests/unit/net_test.ts b/cli/tests/unit/net_test.ts deleted file mode 100644 index fa9790a76..000000000 --- a/cli/tests/unit/net_test.ts +++ /dev/null @@ -1,1274 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertNotEquals, - assertRejects, - assertThrows, - delay, - execCode, - execCode2, - tmpUnixSocketPath, -} from "./test_util.ts"; - -// Since these tests may run in parallel, ensure this port is unique to this file -const listenPort = 4503; -const listenPort2 = 4504; - -let isCI: boolean; -try { - isCI = Deno.env.get("CI") !== undefined; -} catch { - isCI = true; -} - -Deno.test({ permissions: { net: true } }, function netTcpListenClose() { - const listener = Deno.listen({ hostname: "127.0.0.1", port: listenPort }); - assert(listener.addr.transport === "tcp"); - assertEquals(listener.addr.hostname, "127.0.0.1"); - assertEquals(listener.addr.port, listenPort); - assertNotEquals(listener.rid, 0); - listener.close(); -}); - -Deno.test( - { - permissions: { net: true }, - }, - function netUdpListenClose() { - const socket = Deno.listenDatagram({ - hostname: "127.0.0.1", - port: listenPort, - transport: "udp", - }); - assert(socket.addr.transport === "udp"); - assertEquals(socket.addr.hostname, "127.0.0.1"); - assertEquals(socket.addr.port, listenPort); - socket.close(); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - function netUnixListenClose() { - const filePath = tmpUnixSocketPath(); - const socket = Deno.listen({ - path: filePath, - transport: "unix", - }); - assert(socket.addr.transport === "unix"); - assertEquals(socket.addr.path, filePath); - socket.close(); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - function netUnixPacketListenClose() { - const filePath = tmpUnixSocketPath(); - const socket = Deno.listenDatagram({ - path: filePath, - transport: "unixpacket", - }); - assert(socket.addr.transport === "unixpacket"); - assertEquals(socket.addr.path, filePath); - socket.close(); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: false }, - }, - function netUnixListenWritePermission() { - assertThrows(() => { - const filePath = tmpUnixSocketPath(); - const socket = Deno.listen({ - path: filePath, - transport: "unix", - }); - assert(socket.addr.transport === "unix"); - assertEquals(socket.addr.path, filePath); - socket.close(); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: false }, - }, - function netUnixPacketListenWritePermission() { - assertThrows(() => { - const filePath = tmpUnixSocketPath(); - const socket = Deno.listenDatagram({ - path: filePath, - transport: "unixpacket", - }); - assert(socket.addr.transport === "unixpacket"); - assertEquals(socket.addr.path, filePath); - socket.close(); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function netTcpCloseWhileAccept() { - const listener = Deno.listen({ port: listenPort }); - const p = listener.accept(); - listener.close(); - // TODO(piscisaureus): the error type should be `Interrupted` here, which - // gets thrown, but then ext/net catches it and rethrows `BadResource`. - await assertRejects( - () => p, - Deno.errors.BadResource, - "Listener has been closed", - ); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - async function netUnixCloseWhileAccept() { - const filePath = tmpUnixSocketPath(); - const listener = Deno.listen({ - path: filePath, - transport: "unix", - }); - const p = listener.accept(); - listener.close(); - await assertRejects( - () => p, - Deno.errors.BadResource, - "Listener has been closed", - ); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function netTcpConcurrentAccept() { - const listener = Deno.listen({ port: 4510 }); - let acceptErrCount = 0; - const checkErr = (e: Error) => { - if (e.message === "Listener has been closed") { - assertEquals(acceptErrCount, 1); - } else if (e.message === "Another accept task is ongoing") { - acceptErrCount++; - } else { - throw new Error("Unexpected error message"); - } - }; - const p = listener.accept().catch(checkErr); - const p1 = listener.accept().catch(checkErr); - await Promise.race([p, p1]); - listener.close(); - await Promise.all([p, p1]); - assertEquals(acceptErrCount, 1); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - async function netUnixConcurrentAccept() { - const filePath = tmpUnixSocketPath(); - const listener = Deno.listen({ transport: "unix", path: filePath }); - let acceptErrCount = 0; - const checkErr = (e: Error) => { - if (e.message === "Listener has been closed") { - assertEquals(acceptErrCount, 1); - } else if (e instanceof Deno.errors.Busy) { // "Listener already in use" - acceptErrCount++; - } else { - throw e; - } - }; - const p = listener.accept().catch(checkErr); - const p1 = listener.accept().catch(checkErr); - await Promise.race([p, p1]); - listener.close(); - await Promise.all([p, p1]); - assertEquals(acceptErrCount, 1); - }, -); - -Deno.test({ permissions: { net: true } }, async function netTcpDialListen() { - const listener = Deno.listen({ port: listenPort }); - listener.accept().then( - async (conn) => { - assert(conn.remoteAddr != null); - assert(conn.localAddr.transport === "tcp"); - assertEquals(conn.localAddr.hostname, "127.0.0.1"); - assertEquals(conn.localAddr.port, listenPort); - await conn.write(new Uint8Array([1, 2, 3])); - conn.close(); - }, - ); - - const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort }); - assert(conn.remoteAddr.transport === "tcp"); - assertEquals(conn.remoteAddr.hostname, "127.0.0.1"); - assertEquals(conn.remoteAddr.port, listenPort); - assert(conn.localAddr != null); - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assertEquals(3, readResult); - assertEquals(1, buf[0]); - assertEquals(2, buf[1]); - assertEquals(3, buf[2]); - assert(conn.rid > 0); - - assert(readResult !== null); - - const readResult2 = await conn.read(buf); - assertEquals(readResult2, null); - - listener.close(); - conn.close(); -}); - -Deno.test({ permissions: { net: true } }, async function netTcpSetNoDelay() { - const listener = Deno.listen({ port: listenPort }); - listener.accept().then( - async (conn) => { - assert(conn.remoteAddr != null); - assert(conn.localAddr.transport === "tcp"); - assertEquals(conn.localAddr.hostname, "127.0.0.1"); - assertEquals(conn.localAddr.port, listenPort); - await conn.write(new Uint8Array([1, 2, 3])); - conn.close(); - }, - ); - - const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort }); - conn.setNoDelay(true); - assert(conn.remoteAddr.transport === "tcp"); - assertEquals(conn.remoteAddr.hostname, "127.0.0.1"); - assertEquals(conn.remoteAddr.port, listenPort); - assert(conn.localAddr != null); - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assertEquals(3, readResult); - assertEquals(1, buf[0]); - assertEquals(2, buf[1]); - assertEquals(3, buf[2]); - assert(conn.rid > 0); - - assert(readResult !== null); - - const readResult2 = await conn.read(buf); - assertEquals(readResult2, null); - - listener.close(); - conn.close(); -}); - -Deno.test({ permissions: { net: true } }, async function netTcpSetKeepAlive() { - const listener = Deno.listen({ port: listenPort }); - listener.accept().then( - async (conn) => { - assert(conn.remoteAddr != null); - assert(conn.localAddr.transport === "tcp"); - assertEquals(conn.localAddr.hostname, "127.0.0.1"); - assertEquals(conn.localAddr.port, listenPort); - await conn.write(new Uint8Array([1, 2, 3])); - conn.close(); - }, - ); - - const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort }); - conn.setKeepAlive(true); - assert(conn.remoteAddr.transport === "tcp"); - assertEquals(conn.remoteAddr.hostname, "127.0.0.1"); - assertEquals(conn.remoteAddr.port, listenPort); - assert(conn.localAddr != null); - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assertEquals(3, readResult); - assertEquals(1, buf[0]); - assertEquals(2, buf[1]); - assertEquals(3, buf[2]); - assert(conn.rid > 0); - - assert(readResult !== null); - - const readResult2 = await conn.read(buf); - assertEquals(readResult2, null); - - listener.close(); - conn.close(); -}); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - async function netUnixDialListen() { - const filePath = tmpUnixSocketPath(); - const listener = Deno.listen({ path: filePath, transport: "unix" }); - listener.accept().then( - async (conn) => { - assert(conn.remoteAddr != null); - assert(conn.localAddr.transport === "unix"); - assertEquals(conn.localAddr.path, filePath); - await conn.write(new Uint8Array([1, 2, 3])); - conn.close(); - }, - ); - const conn = await Deno.connect({ path: filePath, transport: "unix" }); - assert(conn.remoteAddr.transport === "unix"); - assertEquals(conn.remoteAddr.path, filePath); - assert(conn.remoteAddr != null); - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assertEquals(3, readResult); - assertEquals(1, buf[0]); - assertEquals(2, buf[1]); - assertEquals(3, buf[2]); - assert(conn.rid > 0); - - assert(readResult !== null); - - const readResult2 = await conn.read(buf); - assertEquals(readResult2, null); - - listener.close(); - conn.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function netUdpSendReceive() { - const alice = Deno.listenDatagram({ port: listenPort, transport: "udp" }); - assert(alice.addr.transport === "udp"); - assertEquals(alice.addr.port, listenPort); - assertEquals(alice.addr.hostname, "127.0.0.1"); - - const bob = Deno.listenDatagram({ port: listenPort2, transport: "udp" }); - assert(bob.addr.transport === "udp"); - assertEquals(bob.addr.port, listenPort2); - assertEquals(bob.addr.hostname, "127.0.0.1"); - - const sent = new Uint8Array([1, 2, 3]); - const byteLength = await alice.send(sent, bob.addr); - - assertEquals(byteLength, 3); - - const [recvd, remote] = await bob.receive(); - assert(remote.transport === "udp"); - assertEquals(remote.port, listenPort); - assertEquals(recvd.length, 3); - assertEquals(1, recvd[0]); - assertEquals(2, recvd[1]); - assertEquals(3, recvd[2]); - alice.close(); - bob.close(); - }, -); - -Deno.test( - { permissions: { net: true }, ignore: true }, - async function netUdpSendReceiveBroadcast() { - // Must bind sender to an address that can send to the broadcast address on MacOS. - // Macos will give us error 49 when sending the broadcast packet if we omit hostname here. - const alice = Deno.listenDatagram({ - port: listenPort, - transport: "udp", - hostname: "0.0.0.0", - }); - - const bob = Deno.listenDatagram({ - port: listenPort, - transport: "udp", - hostname: "0.0.0.0", - }); - assert(bob.addr.transport === "udp"); - assertEquals(bob.addr.port, listenPort); - assertEquals(bob.addr.hostname, "0.0.0.0"); - - const broadcastAddr = { ...bob.addr, hostname: "255.255.255.255" }; - - const sent = new Uint8Array([1, 2, 3]); - const byteLength = await alice.send(sent, broadcastAddr); - - assertEquals(byteLength, 3); - const [recvd, remote] = await bob.receive(); - assert(remote.transport === "udp"); - assertEquals(remote.port, listenPort); - assertEquals(recvd.length, 3); - assertEquals(1, recvd[0]); - assertEquals(2, recvd[1]); - assertEquals(3, recvd[2]); - alice.close(); - bob.close(); - }, -); - -Deno.test( - { permissions: { net: true }, ignore: true }, - async function netUdpMulticastV4() { - const listener = Deno.listenDatagram({ - hostname: "0.0.0.0", - port: 5353, - transport: "udp", - reuseAddress: true, - }); - - const membership = await listener.joinMulticastV4( - "224.0.0.251", - "127.0.0.1", - ); - - membership.setLoopback(true); - membership.setLoopback(false); - membership.setTTL(50); - membership.leave(); - listener.close(); - }, -); - -Deno.test( - { permissions: { net: true }, ignore: true }, - async function netUdpMulticastV6() { - const listener = Deno.listenDatagram({ - hostname: "::", - port: 5353, - transport: "udp", - reuseAddress: true, - }); - - const membership = await listener.joinMulticastV6( - "ff02::fb", - 1, - ); - - membership.setLoopback(true); - membership.setLoopback(false); - membership.leave(); - listener.close(); - }, -); - -Deno.test( - { permissions: { net: true }, ignore: true }, - async function netUdpSendReceiveMulticastv4() { - const alice = Deno.listenDatagram({ - hostname: "0.0.0.0", - port: 5353, - transport: "udp", - reuseAddress: true, - loopback: true, - }); - - const bob = Deno.listenDatagram({ - hostname: "0.0.0.0", - port: 5353, - transport: "udp", - reuseAddress: true, - }); - - const aliceMembership = await alice.joinMulticastV4( - "224.0.0.1", - "0.0.0.0", - ); - - const bobMembership = await bob.joinMulticastV4("224.0.0.1", "0.0.0.0"); - - const sent = new Uint8Array([1, 2, 3]); - - await alice.send(sent, { - hostname: "224.0.0.1", - port: 5353, - transport: "udp", - }); - - const [recvd, remote] = await bob.receive(); - - assert(remote.transport === "udp"); - assertEquals(remote.port, 5353); - assertEquals(recvd.length, 3); - assertEquals(1, recvd[0]); - assertEquals(2, recvd[1]); - assertEquals(3, recvd[2]); - - aliceMembership.leave(); - bobMembership.leave(); - - alice.close(); - bob.close(); - }, -); - -Deno.test( - { permissions: { net: true }, ignore: true }, - async function netUdpMulticastLoopbackOption() { - // Must bind sender to an address that can send to the broadcast address on MacOS. - // Macos will give us error 49 when sending the broadcast packet if we omit hostname here. - const listener = Deno.listenDatagram({ - port: 5353, - transport: "udp", - hostname: "0.0.0.0", - loopback: true, - reuseAddress: true, - }); - - const membership = await listener.joinMulticastV4( - "224.0.0.1", - "0.0.0.0", - ); - - // await membership.setLoopback(true); - - const sent = new Uint8Array([1, 2, 3]); - const byteLength = await listener.send(sent, { - hostname: "224.0.0.1", - port: 5353, - transport: "udp", - }); - - assertEquals(byteLength, 3); - const [recvd, remote] = await listener.receive(); - assert(remote.transport === "udp"); - assertEquals(remote.port, 5353); - assertEquals(recvd.length, 3); - assertEquals(1, recvd[0]); - assertEquals(2, recvd[1]); - assertEquals(3, recvd[2]); - membership.leave(); - listener.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function netUdpConcurrentSendReceive() { - const socket = Deno.listenDatagram({ port: listenPort, transport: "udp" }); - assert(socket.addr.transport === "udp"); - assertEquals(socket.addr.port, listenPort); - assertEquals(socket.addr.hostname, "127.0.0.1"); - - const recvPromise = socket.receive(); - - const sendBuf = new Uint8Array([1, 2, 3]); - const sendLen = await socket.send(sendBuf, socket.addr); - assertEquals(sendLen, 3); - - const [recvBuf, _recvAddr] = await recvPromise; - assertEquals(recvBuf.length, 3); - assertEquals(1, recvBuf[0]); - assertEquals(2, recvBuf[1]); - assertEquals(3, recvBuf[2]); - - socket.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function netUdpBorrowMutError() { - const socket = Deno.listenDatagram({ - port: listenPort, - transport: "udp", - }); - // Panic happened on second send: BorrowMutError - const a = socket.send(new Uint8Array(), socket.addr); - const b = socket.send(new Uint8Array(), socket.addr); - await Promise.all([a, b]); - socket.close(); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - async function netUnixPacketSendReceive() { - const aliceFilePath = tmpUnixSocketPath(); - const alice = Deno.listenDatagram({ - path: aliceFilePath, - transport: "unixpacket", - }); - assert(alice.addr.transport === "unixpacket"); - assertEquals(alice.addr.path, aliceFilePath); - - const bobFilePath = tmpUnixSocketPath(); - const bob = Deno.listenDatagram({ - path: bobFilePath, - transport: "unixpacket", - }); - assert(bob.addr.transport === "unixpacket"); - assertEquals(bob.addr.path, bobFilePath); - - const sent = new Uint8Array([1, 2, 3]); - const byteLength = await alice.send(sent, bob.addr); - assertEquals(byteLength, 3); - - const [recvd, remote] = await bob.receive(); - assert(remote.transport === "unixpacket"); - assertEquals(remote.path, aliceFilePath); - assertEquals(recvd.length, 3); - assertEquals(1, recvd[0]); - assertEquals(2, recvd[1]); - assertEquals(3, recvd[2]); - alice.close(); - bob.close(); - }, -); - -// TODO(lucacasonato): support concurrent reads and writes on unixpacket sockets -Deno.test( - { ignore: true, permissions: { read: true, write: true } }, - async function netUnixPacketConcurrentSendReceive() { - const filePath = tmpUnixSocketPath(); - const socket = Deno.listenDatagram({ - path: filePath, - transport: "unixpacket", - }); - assert(socket.addr.transport === "unixpacket"); - assertEquals(socket.addr.path, filePath); - - const recvPromise = socket.receive(); - - const sendBuf = new Uint8Array([1, 2, 3]); - const sendLen = await socket.send(sendBuf, socket.addr); - assertEquals(sendLen, 3); - - const [recvBuf, _recvAddr] = await recvPromise; - assertEquals(recvBuf.length, 3); - assertEquals(1, recvBuf[0]); - assertEquals(2, recvBuf[1]); - assertEquals(3, recvBuf[2]); - - socket.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function netTcpListenIteratorBreakClosesResource() { - async function iterate(listener: Deno.Listener) { - let i = 0; - - for await (const conn of listener) { - conn.close(); - i++; - - if (i > 1) { - break; - } - } - } - - const addr = { hostname: "127.0.0.1", port: 8888 }; - const listener = Deno.listen(addr); - const iteratePromise = iterate(listener); - - await delay(100); - const conn1 = await Deno.connect(addr); - conn1.close(); - const conn2 = await Deno.connect(addr); - conn2.close(); - - await iteratePromise; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function netTcpListenCloseWhileIterating() { - const listener = Deno.listen({ port: 8001 }); - const nextWhileClosing = listener[Symbol.asyncIterator]().next(); - listener.close(); - assertEquals(await nextWhileClosing, { value: undefined, done: true }); - - const nextAfterClosing = listener[Symbol.asyncIterator]().next(); - assertEquals(await nextAfterClosing, { value: undefined, done: true }); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function netUdpListenCloseWhileIterating() { - const socket = Deno.listenDatagram({ port: 8000, transport: "udp" }); - const nextWhileClosing = socket[Symbol.asyncIterator]().next(); - socket.close(); - assertEquals(await nextWhileClosing, { value: undefined, done: true }); - - const nextAfterClosing = socket[Symbol.asyncIterator]().next(); - assertEquals(await nextAfterClosing, { value: undefined, done: true }); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - async function netUnixListenCloseWhileIterating() { - const filePath = tmpUnixSocketPath(); - const socket = Deno.listen({ path: filePath, transport: "unix" }); - const nextWhileClosing = socket[Symbol.asyncIterator]().next(); - socket.close(); - assertEquals(await nextWhileClosing, { value: undefined, done: true }); - - const nextAfterClosing = socket[Symbol.asyncIterator]().next(); - assertEquals(await nextAfterClosing, { value: undefined, done: true }); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - async function netUnixPacketListenCloseWhileIterating() { - const filePath = tmpUnixSocketPath(); - const socket = Deno.listenDatagram({ - path: filePath, - transport: "unixpacket", - }); - const nextWhileClosing = socket[Symbol.asyncIterator]().next(); - socket.close(); - assertEquals(await nextWhileClosing, { value: undefined, done: true }); - - const nextAfterClosing = socket[Symbol.asyncIterator]().next(); - assertEquals(await nextAfterClosing, { value: undefined, done: true }); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function netListenAsyncIterator() { - const addr = { hostname: "127.0.0.1", port: listenPort }; - const listener = Deno.listen(addr); - const runAsyncIterator = async () => { - for await (const conn of listener) { - await conn.write(new Uint8Array([1, 2, 3])); - conn.close(); - } - }; - runAsyncIterator(); - const conn = await Deno.connect(addr); - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assertEquals(3, readResult); - assertEquals(1, buf[0]); - assertEquals(2, buf[1]); - assertEquals(3, buf[2]); - assert(conn.rid > 0); - - assert(readResult !== null); - - const readResult2 = await conn.read(buf); - assertEquals(readResult2, null); - - listener.close(); - conn.close(); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - async function netCloseWriteSuccess() { - const addr = { hostname: "127.0.0.1", port: listenPort }; - const listener = Deno.listen(addr); - const { promise: closePromise, resolve } = Promise.withResolvers<void>(); - listener.accept().then(async (conn) => { - await conn.write(new Uint8Array([1, 2, 3])); - await closePromise; - conn.close(); - }); - const conn = await Deno.connect(addr); - conn.closeWrite(); // closing write - const buf = new Uint8Array(1024); - // Check read not impacted - const readResult = await conn.read(buf); - assertEquals(3, readResult); - assertEquals(1, buf[0]); - assertEquals(2, buf[1]); - assertEquals(3, buf[2]); - // Verify that the write end of the socket is closed. - // TODO(piscisaureus): assert that thrown error is of a specific type. - await assertRejects(async () => { - await conn.write(new Uint8Array([1, 2, 3])); - }); - resolve(); - listener.close(); - conn.close(); - }, -); - -Deno.test( - { - // https://github.com/denoland/deno/issues/11580 - ignore: Deno.build.os === "darwin" && isCI, - permissions: { net: true }, - }, - async function netHangsOnClose() { - let acceptedConn: Deno.Conn; - - async function iteratorReq(listener: Deno.Listener) { - const p = new Uint8Array(10); - const conn = await listener.accept(); - acceptedConn = conn; - - try { - while (true) { - const nread = await conn.read(p); - if (nread === null) { - break; - } - await conn.write(new Uint8Array([1, 2, 3])); - } - } catch (err) { - assert(err); - assert(err instanceof Deno.errors.Interrupted); - } - } - - const addr = { hostname: "127.0.0.1", port: listenPort }; - const listener = Deno.listen(addr); - const listenerPromise = iteratorReq(listener); - const connectionPromise = (async () => { - const conn = await Deno.connect(addr); - await conn.write(new Uint8Array([1, 2, 3, 4])); - const buf = new Uint8Array(10); - await conn.read(buf); - conn!.close(); - acceptedConn!.close(); - listener.close(); - })(); - - await Promise.all([ - listenerPromise, - connectionPromise, - ]); - }, -); - -Deno.test( - { - permissions: { net: true }, - }, - function netExplicitUndefinedHostname() { - const listener = Deno.listen({ hostname: undefined, port: 8080 }); - assertEquals((listener.addr as Deno.NetAddr).hostname, "0.0.0.0"); - listener.close(); - }, -); - -Deno.test( - { - ignore: Deno.build.os !== "linux", - permissions: { read: true, write: true }, - }, - function netUnixAbstractPathShouldNotPanic() { - const listener = Deno.listen({ - path: "\0aaa", - transport: "unix", - }); - assert("not panic"); - listener.close(); - }, -); - -Deno.test({ permissions: { net: true } }, async function whatwgStreams() { - const server = (async () => { - const listener = Deno.listen({ hostname: "127.0.0.1", port: listenPort }); - const conn = await listener.accept(); - await conn.readable.pipeTo(conn.writable); - listener.close(); - })(); - - const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort }); - const reader = conn.readable.getReader(); - const writer = conn.writable.getWriter(); - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - const data = encoder.encode("Hello World"); - - await writer.write(data); - const { value, done } = await reader.read(); - assert(!done); - assertEquals(decoder.decode(value), "Hello World"); - await reader.cancel(); - await server; -}); - -Deno.test( - { permissions: { read: true } }, - async function readableStreamTextEncoderPipe() { - const filename = "cli/tests/testdata/assets/hello.txt"; - const file = await Deno.open(filename); - const readable = file.readable.pipeThrough(new TextDecoderStream()); - const chunks = []; - for await (const chunk of readable) { - chunks.push(chunk); - } - assertEquals(chunks.length, 1); - assertEquals(chunks[0].length, 12); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writableStream() { - const path = await Deno.makeTempFile(); - const file = await Deno.open(path, { write: true }); - assert(file.writable instanceof WritableStream); - const readable = new ReadableStream({ - start(controller) { - controller.enqueue(new TextEncoder().encode("hello ")); - controller.enqueue(new TextEncoder().encode("world!")); - controller.close(); - }, - }); - await readable.pipeTo(file.writable); - const res = await Deno.readTextFile(path); - assertEquals(res, "hello world!"); - }, -); - -Deno.test( - { permissions: { read: true, run: true } }, - async function netListenUnref() { - const [statusCode, _output] = await execCode(` - async function main() { - const listener = Deno.listen({ port: ${listenPort} }); - listener.unref(); - await listener.accept(); // This doesn't block the program from exiting - } - main(); - `); - assertEquals(statusCode, 0); - }, -); - -Deno.test( - { permissions: { read: true, run: true } }, - async function netListenUnref2() { - const [statusCode, _output] = await execCode(` - async function main() { - const listener = Deno.listen({ port: ${listenPort} }); - await listener.accept(); - listener.unref(); - await listener.accept(); // The program exits here - throw new Error(); // The program doesn't reach here - } - main(); - const conn = await Deno.connect({ port: ${listenPort} }); - conn.close(); - `); - assertEquals(statusCode, 0); - }, -); - -Deno.test( - { permissions: { read: true, run: true, net: true } }, - async function netListenUnrefAndRef() { - const p = execCode2(` - async function main() { - const listener = Deno.listen({ port: ${listenPort} }); - listener.unref(); - listener.ref(); // This restores 'ref' state of listener - console.log("started"); - await listener.accept(); - console.log("accepted") - } - main(); - `); - await p.waitStdoutText("started"); - const conn = await Deno.connect({ port: listenPort }); - conn.close(); - const [statusCode, output] = await p.finished(); - assertEquals(statusCode, 0); - assertEquals(output.trim(), "started\naccepted"); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function netListenUnrefConcurrentAccept() { - const timer = setTimeout(() => {}, 1000); - const listener = Deno.listen({ port: listenPort }); - listener.accept().catch(() => {}); - listener.unref(); - // Unref'd listener still causes Busy error - // on concurrent accept calls. - await assertRejects(async () => { - await listener.accept(); // The program exits here - }, Deno.errors.Busy); - listener.close(); - clearTimeout(timer); - }, -); - -Deno.test({ - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, -}, function netUnixListenAddrAlreadyInUse() { - const filePath = tmpUnixSocketPath(); - const listener = Deno.listen({ path: filePath, transport: "unix" }); - assertThrows( - () => { - Deno.listen({ path: filePath, transport: "unix" }); - }, - Deno.errors.AddrInUse, - ); - listener.close(); -}); - -Deno.test( - { permissions: { net: true, read: true, run: true } }, - async function netConnUnref() { - const listener = Deno.listen({ port: listenPort }); - const intervalId = setInterval(() => {}); // This keeps event loop alive. - - const program = execCode(` - async function main() { - const conn = await Deno.connect({ port: ${listenPort} }); - conn.unref(); - await conn.read(new Uint8Array(10)); // The program exits here - throw new Error(); // The program doesn't reach here - } - main(); - `); - const conn = await listener.accept(); - const [statusCode, _output] = await program; - conn.close(); - listener.close(); - clearInterval(intervalId); - assertEquals(statusCode, 0); - }, -); - -Deno.test( - { permissions: { net: true, read: true, run: true } }, - async function netConnUnrefReadable() { - const listener = Deno.listen({ port: listenPort }); - const intervalId = setInterval(() => {}); // This keeps event loop alive. - - const program = execCode(` - async function main() { - const conn = await Deno.connect({ port: ${listenPort} }); - conn.unref(); - const reader = conn.readable.getReader(); - await reader.read(); // The program exits here - throw new Error(); // The program doesn't reach here - } - main(); - `); - const conn = await listener.accept(); - const [statusCode, _output] = await program; - conn.close(); - listener.close(); - clearInterval(intervalId); - assertEquals(statusCode, 0); - }, -); - -Deno.test({ permissions: { net: true } }, async function netTcpReuseAddr() { - const listener1 = Deno.listen({ - hostname: "127.0.0.1", - port: listenPort, - }); - listener1.accept().then( - (conn) => { - conn.close(); - }, - ); - - const conn1 = await Deno.connect({ hostname: "127.0.0.1", port: listenPort }); - const buf1 = new Uint8Array(1024); - await conn1.read(buf1); - listener1.close(); - conn1.close(); - - const listener2 = Deno.listen({ - hostname: "127.0.0.1", - port: listenPort, - }); - - listener2.accept().then( - (conn) => { - conn.close(); - }, - ); - - const conn2 = await Deno.connect({ hostname: "127.0.0.1", port: listenPort }); - const buf2 = new Uint8Array(1024); - await conn2.read(buf2); - - listener2.close(); - conn2.close(); -}); - -Deno.test( - { permissions: { net: true } }, - async function netUdpReuseAddr() { - const sender = Deno.listenDatagram({ - port: 4002, - transport: "udp", - }); - const listener1 = Deno.listenDatagram({ - port: 4000, - transport: "udp", - reuseAddress: true, - }); - const listener2 = Deno.listenDatagram({ - port: 4000, - transport: "udp", - reuseAddress: true, - }); - - const sent = new Uint8Array([1, 2, 3]); - await sender.send(sent, listener1.addr); - await Promise.any([listener1.receive(), listener2.receive()]).then( - ([recvd, remote]) => { - assert(remote.transport === "udp"); - assertEquals(recvd.length, 3); - assertEquals(1, recvd[0]); - assertEquals(2, recvd[1]); - assertEquals(3, recvd[2]); - }, - ); - sender.close(); - listener1.close(); - listener2.close(); - }, -); - -Deno.test( - { permissions: { net: true } }, - function netUdpNoReuseAddr() { - let listener1; - try { - listener1 = Deno.listenDatagram({ - port: 4001, - transport: "udp", - reuseAddress: false, - }); - } catch (err) { - assert(err); - assert(err instanceof Deno.errors.AddrInUse); // AddrInUse from previous test - } - - assertThrows(() => { - Deno.listenDatagram({ - port: 4001, - transport: "udp", - reuseAddress: false, - }); - }, Deno.errors.AddrInUse); - if (typeof listener1 !== "undefined") { - listener1.close(); - } - }, -); - -Deno.test({ - ignore: Deno.build.os !== "linux", - permissions: { net: true }, -}, async function netTcpListenReusePort() { - const port = 4003; - const listener1 = Deno.listen({ port, reusePort: true }); - const listener2 = Deno.listen({ port, reusePort: true }); - let p1; - let p2; - let listener1Recv = false; - let listener2Recv = false; - while (!listener1Recv || !listener2Recv) { - if (!p1) { - p1 = listener1.accept().then((conn) => { - conn.close(); - listener1Recv = true; - p1 = undefined; - }).catch(() => {}); - } - if (!p2) { - p2 = listener2.accept().then((conn) => { - conn.close(); - listener2Recv = true; - p2 = undefined; - }).catch(() => {}); - } - const conn = await Deno.connect({ port }); - conn.close(); - await Promise.race([p1, p2]); - } - listener1.close(); - listener2.close(); -}); - -Deno.test({ - ignore: Deno.build.os === "linux", - permissions: { net: true }, -}, function netTcpListenReusePortDoesNothing() { - const listener1 = Deno.listen({ port: 4003, reusePort: true }); - assertThrows(() => { - Deno.listen({ port: 4003, reusePort: true }); - }, Deno.errors.AddrInUse); - listener1.close(); -}); - -Deno.test({ - permissions: { net: true }, -}, function netTcpListenDoesNotThrowOnStringPort() { - // @ts-ignore String port is not allowed by typing, but it shouldn't throw - // for backwards compatibility. - const listener = Deno.listen({ hostname: "localhost", port: "0" }); - listener.close(); -}); - -Deno.test( - { permissions: { net: true } }, - async function listenerExplicitResourceManagement() { - let done: Promise<Deno.errors.BadResource>; - - { - using listener = Deno.listen({ port: listenPort }); - - done = assertRejects( - () => listener.accept(), - Deno.errors.BadResource, - ); - } - - await done; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function listenerExplicitResourceManagementManualClose() { - using listener = Deno.listen({ port: listenPort }); - listener.close(); - await assertRejects( // definitely closed - () => listener.accept(), - Deno.errors.BadResource, - ); - // calling [Symbol.dispose] after manual close is a no-op - }, -); diff --git a/cli/tests/unit/network_interfaces_test.ts b/cli/tests/unit/network_interfaces_test.ts deleted file mode 100644 index 160efbfe6..000000000 --- a/cli/tests/unit/network_interfaces_test.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assert } from "./test_util.ts"; - -Deno.test( - { - name: "Deno.networkInterfaces", - permissions: { sys: ["networkInterfaces"] }, - }, - () => { - const networkInterfaces = Deno.networkInterfaces(); - assert(Array.isArray(networkInterfaces)); - assert(networkInterfaces.length > 0); - for ( - const { name, family, address, netmask, scopeid, cidr, mac } - of networkInterfaces - ) { - assert(typeof name === "string"); - assert(family === "IPv4" || family === "IPv6"); - assert(typeof address === "string"); - assert(typeof netmask === "string"); - assert( - (family === "IPv6" && typeof scopeid === "number") || - (family === "IPv4" && scopeid === null), - ); - assert(typeof cidr === "string"); - assert(typeof mac === "string"); - } - }, -); diff --git a/cli/tests/unit/ops_test.ts b/cli/tests/unit/ops_test.ts deleted file mode 100644 index 4a0daa0a5..000000000 --- a/cli/tests/unit/ops_test.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -const EXPECTED_OP_COUNT = 15; - -Deno.test(function checkExposedOps() { - // @ts-ignore TS doesn't allow to index with symbol - const core = Deno[Deno.internal].core; - const opNames = Object.keys(core.ops); - - if (opNames.length !== EXPECTED_OP_COUNT) { - throw new Error( - `Expected ${EXPECTED_OP_COUNT} ops, but got ${opNames.length}:\n${ - opNames.join("\n") - }`, - ); - } -}); diff --git a/cli/tests/unit/os_test.ts b/cli/tests/unit/os_test.ts deleted file mode 100644 index e24494854..000000000 --- a/cli/tests/unit/os_test.ts +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertNotEquals, - assertThrows, -} from "./test_util.ts"; - -Deno.test({ permissions: { env: true } }, function envSuccess() { - Deno.env.set("TEST_VAR", "A"); - const env = Deno.env.toObject(); - Deno.env.set("TEST_VAR", "B"); - assertEquals(env["TEST_VAR"], "A"); - assertNotEquals(Deno.env.get("TEST_VAR"), env["TEST_VAR"]); -}); - -Deno.test({ permissions: { env: true } }, function envNotFound() { - const r = Deno.env.get("env_var_does_not_exist!"); - assertEquals(r, undefined); -}); - -Deno.test({ permissions: { env: true } }, function deleteEnv() { - Deno.env.set("TEST_VAR", "A"); - assertEquals(Deno.env.get("TEST_VAR"), "A"); - assertEquals(Deno.env.delete("TEST_VAR"), undefined); - assertEquals(Deno.env.get("TEST_VAR"), undefined); -}); - -Deno.test({ permissions: { env: true } }, function hasEnv() { - Deno.env.set("TEST_VAR", "A"); - assert(Deno.env.has("TEST_VAR")); - Deno.env.delete("TEST_VAR"); - assert(!Deno.env.has("TEST_VAR")); -}); - -Deno.test({ permissions: { env: true } }, function avoidEmptyNamedEnv() { - assertThrows(() => Deno.env.set("", "v"), TypeError); - assertThrows(() => Deno.env.set("a=a", "v"), TypeError); - assertThrows(() => Deno.env.set("a\0a", "v"), TypeError); - assertThrows(() => Deno.env.set("TEST_VAR", "v\0v"), TypeError); - - assertThrows(() => Deno.env.get(""), TypeError); - assertThrows(() => Deno.env.get("a=a"), TypeError); - assertThrows(() => Deno.env.get("a\0a"), TypeError); - - assertThrows(() => Deno.env.delete(""), TypeError); - assertThrows(() => Deno.env.delete("a=a"), TypeError); - assertThrows(() => Deno.env.delete("a\0a"), TypeError); -}); - -Deno.test({ permissions: { env: false } }, function envPermissionDenied1() { - assertThrows(() => { - Deno.env.toObject(); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { env: false } }, function envPermissionDenied2() { - assertThrows(() => { - Deno.env.get("PATH"); - }, Deno.errors.PermissionDenied); -}); - -// This test verifies that on Windows, environment variables are -// case-insensitive. Case normalization needs be done using the collation -// that Windows uses, rather than naively using String.toLowerCase(). -Deno.test( - { - ignore: Deno.build.os !== "windows", - permissions: { read: true, env: true, run: true }, - }, - async function envCaseInsensitive() { - // Utility function that runs a Deno subprocess with the environment - // specified in `inputEnv`. The subprocess reads the environment variables - // which are in the keys of `expectedEnv` and writes them to stdout as JSON. - // It is then verified that these match with the values of `expectedEnv`. - const checkChildEnv = async ( - inputEnv: Record<string, string>, - expectedEnv: Record<string, string>, - ) => { - const src = ` - console.log( - ${JSON.stringify(Object.keys(expectedEnv))}.map(k => Deno.env.get(k)) - )`; - const { success, stdout } = await new Deno.Command(Deno.execPath(), { - args: ["eval", src], - env: { ...inputEnv, NO_COLOR: "1" }, - }).output(); - assertEquals(success, true); - const expectedValues = Object.values(expectedEnv); - const actualValues = JSON.parse(new TextDecoder().decode(stdout)); - assertEquals(actualValues, expectedValues); - }; - - assertEquals(Deno.env.get("path"), Deno.env.get("PATH")); - assertEquals(Deno.env.get("Path"), Deno.env.get("PATH")); - - // Check 'foo', 'Foo' and 'Foo' are case folded. - await checkChildEnv({ foo: "X" }, { foo: "X", Foo: "X", FOO: "X" }); - - // Check that 'µ' and 'Μ' are not case folded. - const lc1 = "µ"; - const uc1 = lc1.toUpperCase(); - assertNotEquals(lc1, uc1); - await checkChildEnv( - { [lc1]: "mu", [uc1]: "MU" }, - { [lc1]: "mu", [uc1]: "MU" }, - ); - - // Check that 'dž' and 'DŽ' are folded, but 'Dž' is preserved. - const c2 = "Dž"; - const lc2 = c2.toLowerCase(); - const uc2 = c2.toUpperCase(); - assertNotEquals(c2, lc2); - assertNotEquals(c2, uc2); - await checkChildEnv( - { [c2]: "Dz", [lc2]: "dz" }, - { [c2]: "Dz", [lc2]: "dz", [uc2]: "dz" }, - ); - await checkChildEnv( - { [c2]: "Dz", [uc2]: "DZ" }, - { [c2]: "Dz", [uc2]: "DZ", [lc2]: "DZ" }, - ); - }, -); - -Deno.test({ permissions: { env: true } }, function envInvalidChars() { - assertThrows(() => Deno.env.get(""), TypeError, "Key is an empty string"); - assertThrows( - () => Deno.env.get("\0"), - TypeError, - 'Key contains invalid characters: "\\0"', - ); - assertThrows( - () => Deno.env.get("="), - TypeError, - 'Key contains invalid characters: "="', - ); - assertThrows( - () => Deno.env.set("", "foo"), - TypeError, - "Key is an empty string", - ); - assertThrows( - () => Deno.env.set("\0", "foo"), - TypeError, - 'Key contains invalid characters: "\\0"', - ); - assertThrows( - () => Deno.env.set("=", "foo"), - TypeError, - 'Key contains invalid characters: "="', - ); - assertThrows( - () => Deno.env.set("foo", "\0"), - TypeError, - 'Value contains invalid characters: "\\0"', - ); -}); - -Deno.test(function osPid() { - assertEquals(typeof Deno.pid, "number"); - assert(Deno.pid > 0); -}); - -Deno.test(function osPpid() { - assertEquals(typeof Deno.ppid, "number"); - assert(Deno.ppid > 0); -}); - -Deno.test( - { permissions: { run: true, read: true } }, - async function osPpidIsEqualToPidOfParentProcess() { - const decoder = new TextDecoder(); - const { stdout } = await new Deno.Command(Deno.execPath(), { - args: ["eval", "-p", "--unstable", "Deno.ppid"], - env: { NO_COLOR: "true" }, - }).output(); - - const expected = Deno.pid; - const actual = parseInt(decoder.decode(stdout)); - assertEquals(actual, expected); - }, -); - -Deno.test({ permissions: { read: true } }, function execPath() { - assertNotEquals(Deno.execPath(), ""); -}); - -Deno.test({ permissions: { read: false } }, function execPathPerm() { - assertThrows( - () => { - Deno.execPath(); - }, - Deno.errors.PermissionDenied, - "Requires read access to <exec_path>, run again with the --allow-read flag", - ); -}); - -Deno.test( - { permissions: { sys: ["loadavg"] } }, - function loadavgSuccess() { - const load = Deno.loadavg(); - assertEquals(load.length, 3); - }, -); - -Deno.test({ permissions: { sys: false } }, function loadavgPerm() { - assertThrows(() => { - Deno.loadavg(); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { permissions: { sys: ["hostname"] } }, - function hostnameDir() { - assertNotEquals(Deno.hostname(), ""); - }, -); - -Deno.test( - { permissions: { run: [Deno.execPath()], read: true } }, - // See https://github.com/denoland/deno/issues/16527 - async function hostnameWithoutOtherNetworkUsages() { - const { stdout } = await new Deno.Command(Deno.execPath(), { - args: ["eval", "-p", "Deno.hostname()"], - }).output(); - const hostname = new TextDecoder().decode(stdout).trim(); - assert(hostname.length > 0); - }, -); - -Deno.test({ permissions: { sys: false } }, function hostnamePerm() { - assertThrows(() => { - Deno.hostname(); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { permissions: { sys: ["osRelease"] } }, - function releaseDir() { - assertNotEquals(Deno.osRelease(), ""); - }, -); - -Deno.test({ permissions: { sys: false } }, function releasePerm() { - assertThrows(() => { - Deno.osRelease(); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { sys: ["osUptime"] } }, function osUptime() { - const uptime = Deno.osUptime(); - assert(typeof uptime === "number"); - assert(uptime > 0); -}); - -Deno.test({ permissions: { sys: false } }, function osUptimePerm() { - assertThrows(() => { - Deno.osUptime(); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { permissions: { sys: ["systemMemoryInfo"] } }, - function systemMemoryInfo() { - const info = Deno.systemMemoryInfo(); - assert(info.total >= 0); - assert(info.free >= 0); - assert(info.available >= 0); - assert(info.buffers >= 0); - assert(info.cached >= 0); - assert(info.swapTotal >= 0); - assert(info.swapFree >= 0); - }, -); - -Deno.test({ permissions: { sys: ["uid"] } }, function getUid() { - if (Deno.build.os === "windows") { - assertEquals(Deno.uid(), null); - } else { - const uid = Deno.uid(); - assert(typeof uid === "number"); - assert(uid > 0); - } -}); - -Deno.test({ permissions: { sys: ["gid"] } }, function getGid() { - if (Deno.build.os === "windows") { - assertEquals(Deno.gid(), null); - } else { - const gid = Deno.gid(); - assert(typeof gid === "number"); - assert(gid > 0); - } -}); - -Deno.test(function memoryUsage() { - const mem = Deno.memoryUsage(); - assert(typeof mem.rss === "number"); - assert(typeof mem.heapTotal === "number"); - assert(typeof mem.heapUsed === "number"); - assert(typeof mem.external === "number"); - assert(mem.rss >= mem.heapTotal); -}); diff --git a/cli/tests/unit/path_from_url_test.ts b/cli/tests/unit/path_from_url_test.ts deleted file mode 100644 index b3a6406bc..000000000 --- a/cli/tests/unit/path_from_url_test.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assertEquals, assertThrows } from "./test_util.ts"; - -// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol -const { pathFromURL } = Deno[Deno.internal]; - -Deno.test( - { ignore: Deno.build.os === "windows" }, - function pathFromURLPosix() { - assertEquals( - pathFromURL(new URL("file:///test/directory")), - "/test/directory", - ); - assertEquals(pathFromURL(new URL("file:///space_ .txt")), "/space_ .txt"); - assertThrows(() => pathFromURL(new URL("https://deno.land/welcome.ts"))); - }, -); - -Deno.test( - { ignore: Deno.build.os !== "windows" }, - function pathFromURLWin32() { - assertEquals( - pathFromURL(new URL("file:///c:/windows/test")), - "c:\\windows\\test", - ); - assertEquals( - pathFromURL(new URL("file:///c:/space_ .txt")), - "c:\\space_ .txt", - ); - assertThrows(() => pathFromURL(new URL("https://deno.land/welcome.ts"))); - /* TODO(ry) Add tests for these situations - * ampersand_&.tx file:///D:/weird_names/ampersand_&.txt - * at_@.txt file:///D:/weird_names/at_@.txt - * emoji_🙃.txt file:///D:/weird_names/emoji_%F0%9F%99%83.txt - * percent_%.txt file:///D:/weird_names/percent_%25.txt - * pound_#.txt file:///D:/weird_names/pound_%23.txt - * swapped_surrogate_pair_��.txt file:///D:/weird_names/swapped_surrogate_pair_%EF%BF%BD%EF%BF%BD.txt - */ - }, -); diff --git a/cli/tests/unit/performance_test.ts b/cli/tests/unit/performance_test.ts deleted file mode 100644 index 0c9ed21df..000000000 --- a/cli/tests/unit/performance_test.ts +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertNotStrictEquals, - assertStringIncludes, - assertThrows, -} from "./test_util.ts"; - -Deno.test({ permissions: { hrtime: false } }, async function performanceNow() { - const { promise, resolve } = Promise.withResolvers<void>(); - const start = performance.now(); - let totalTime = 0; - setTimeout(() => { - const end = performance.now(); - totalTime = end - start; - resolve(); - }, 10); - await promise; - assert(totalTime >= 10); -}); - -Deno.test(function timeOrigin() { - const origin = performance.timeOrigin; - - assert(origin > 0); - assert(Date.now() >= origin); -}); - -Deno.test(function performanceToJSON() { - const json = performance.toJSON(); - - assert("timeOrigin" in json); - assert(json.timeOrigin === performance.timeOrigin); - // check there are no other keys - assertEquals(Object.keys(json).length, 1); -}); - -Deno.test(function performanceMark() { - const mark = performance.mark("test"); - assert(mark instanceof PerformanceMark); - assertEquals(mark.detail, null); - assertEquals(mark.name, "test"); - assertEquals(mark.entryType, "mark"); - assert(mark.startTime > 0); - assertEquals(mark.duration, 0); - const entries = performance.getEntries(); - assert(entries[entries.length - 1] === mark); - const markEntries = performance.getEntriesByName("test", "mark"); - assert(markEntries[markEntries.length - 1] === mark); -}); - -Deno.test(function performanceMarkDetail() { - const detail = { foo: "foo" }; - const mark = performance.mark("test", { detail }); - assert(mark instanceof PerformanceMark); - assertEquals(mark.detail, { foo: "foo" }); - assertNotStrictEquals(mark.detail, detail); -}); - -Deno.test(function performanceMarkDetailArrayBuffer() { - const detail = new ArrayBuffer(10); - const mark = performance.mark("test", { detail }); - assert(mark instanceof PerformanceMark); - assertEquals(mark.detail, new ArrayBuffer(10)); - assertNotStrictEquals(mark.detail, detail); -}); - -Deno.test(function performanceMarkDetailSubTypedArray() { - class SubUint8Array extends Uint8Array {} - const detail = new SubUint8Array([1, 2]); - const mark = performance.mark("test", { detail }); - assert(mark instanceof PerformanceMark); - assertEquals(mark.detail, new Uint8Array([1, 2])); - assertNotStrictEquals(mark.detail, detail); -}); - -Deno.test(function performanceMeasure() { - const markName1 = "mark1"; - const measureName1 = "measure1"; - const measureName2 = "measure2"; - const mark1 = performance.mark(markName1); - // Measure against the inaccurate-but-known-good wall clock - const now = new Date().valueOf(); - return new Promise((resolve, reject) => { - setTimeout(() => { - try { - const later = new Date().valueOf(); - const measure1 = performance.measure(measureName1, markName1); - const measure2 = performance.measure( - measureName2, - undefined, - markName1, - ); - assert(measure1 instanceof PerformanceMeasure); - assertEquals(measure1.detail, null); - assertEquals(measure1.name, measureName1); - assertEquals(measure1.entryType, "measure"); - assert(measure1.startTime > 0); - assertEquals(measure2.startTime, 0); - assertEquals(mark1.startTime, measure1.startTime); - assertEquals(mark1.startTime, measure2.duration); - assert( - measure1.duration >= 100, - `duration below 100ms: ${measure1.duration}`, - ); - assert( - measure1.duration < (later - now) * 1.50, - `duration exceeds 150% of wallclock time: ${measure1.duration}ms vs ${ - later - now - }ms`, - ); - const entries = performance.getEntries(); - assert(entries[entries.length - 1] === measure2); - const entriesByName = performance.getEntriesByName( - measureName1, - "measure", - ); - assert(entriesByName[entriesByName.length - 1] === measure1); - const measureEntries = performance.getEntriesByType("measure"); - assert(measureEntries[measureEntries.length - 1] === measure2); - } catch (e) { - return reject(e); - } - resolve(); - }, 100); - }); -}); - -Deno.test(function performanceCustomInspectFunction() { - assertStringIncludes(Deno.inspect(performance), "Performance"); - assertStringIncludes( - Deno.inspect(Performance.prototype), - "Performance", - ); -}); - -Deno.test(function performanceMarkCustomInspectFunction() { - const mark1 = performance.mark("mark1"); - assertStringIncludes(Deno.inspect(mark1), "PerformanceMark"); - assertStringIncludes( - Deno.inspect(PerformanceMark.prototype), - "PerformanceMark", - ); -}); - -Deno.test(function performanceMeasureCustomInspectFunction() { - const measure1 = performance.measure("measure1"); - assertStringIncludes(Deno.inspect(measure1), "PerformanceMeasure"); - assertStringIncludes( - Deno.inspect(PerformanceMeasure.prototype), - "PerformanceMeasure", - ); -}); - -Deno.test(function performanceIllegalConstructor() { - assertThrows(() => new Performance(), TypeError, "Illegal constructor"); - assertEquals(Performance.length, 0); -}); - -Deno.test(function performanceEntryIllegalConstructor() { - assertThrows(() => new PerformanceEntry(), TypeError, "Illegal constructor"); - assertEquals(PerformanceEntry.length, 0); -}); - -Deno.test(function performanceMeasureIllegalConstructor() { - assertThrows( - () => new PerformanceMeasure(), - TypeError, - "Illegal constructor", - ); -}); - -Deno.test(function performanceIsEventTarget() { - assert(performance instanceof EventTarget); - - return new Promise((resolve) => { - const handler = () => { - resolve(); - }; - - performance.addEventListener("test", handler, { once: true }); - performance.dispatchEvent(new Event("test")); - }); -}); diff --git a/cli/tests/unit/permissions_test.ts b/cli/tests/unit/permissions_test.ts deleted file mode 100644 index 4dab0696a..000000000 --- a/cli/tests/unit/permissions_test.ts +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertRejects, - assertThrows, -} from "./test_util.ts"; - -Deno.test(async function permissionInvalidName() { - await assertRejects(async () => { - // deno-lint-ignore no-explicit-any - await Deno.permissions.query({ name: "foo" as any }); - }, TypeError); -}); - -Deno.test(function permissionInvalidNameSync() { - assertThrows(() => { - // deno-lint-ignore no-explicit-any - Deno.permissions.querySync({ name: "foo" as any }); - }, TypeError); -}); - -Deno.test(async function permissionNetInvalidHost() { - await assertRejects(async () => { - await Deno.permissions.query({ name: "net", host: ":" }); - }, URIError); -}); - -Deno.test(function permissionNetInvalidHostSync() { - assertThrows(() => { - Deno.permissions.querySync({ name: "net", host: ":" }); - }, URIError); -}); - -Deno.test(async function permissionSysValidKind() { - await Deno.permissions.query({ name: "sys", kind: "loadavg" }); - await Deno.permissions.query({ name: "sys", kind: "osRelease" }); - await Deno.permissions.query({ name: "sys", kind: "osUptime" }); - await Deno.permissions.query({ name: "sys", kind: "networkInterfaces" }); - await Deno.permissions.query({ name: "sys", kind: "systemMemoryInfo" }); - await Deno.permissions.query({ name: "sys", kind: "hostname" }); - await Deno.permissions.query({ name: "sys", kind: "uid" }); - await Deno.permissions.query({ name: "sys", kind: "gid" }); - await Deno.permissions.query({ name: "sys", kind: "cpus" }); -}); - -Deno.test(function permissionSysValidKindSync() { - Deno.permissions.querySync({ name: "sys", kind: "loadavg" }); - Deno.permissions.querySync({ name: "sys", kind: "osRelease" }); - Deno.permissions.querySync({ name: "sys", kind: "networkInterfaces" }); - Deno.permissions.querySync({ name: "sys", kind: "systemMemoryInfo" }); - Deno.permissions.querySync({ name: "sys", kind: "hostname" }); - Deno.permissions.querySync({ name: "sys", kind: "uid" }); - Deno.permissions.querySync({ name: "sys", kind: "gid" }); - Deno.permissions.querySync({ name: "sys", kind: "cpus" }); -}); - -Deno.test(async function permissionSysInvalidKind() { - await assertRejects(async () => { - // deno-lint-ignore no-explicit-any - await Deno.permissions.query({ name: "sys", kind: "abc" as any }); - }, TypeError); -}); - -Deno.test(function permissionSysInvalidKindSync() { - assertThrows(() => { - // deno-lint-ignore no-explicit-any - Deno.permissions.querySync({ name: "sys", kind: "abc" as any }); - }, TypeError); -}); - -Deno.test(async function permissionQueryReturnsEventTarget() { - const status = await Deno.permissions.query({ name: "hrtime" }); - assert(["granted", "denied", "prompt"].includes(status.state)); - let called = false; - status.addEventListener("change", () => { - called = true; - }); - status.dispatchEvent(new Event("change")); - assert(called); - assert(status === (await Deno.permissions.query({ name: "hrtime" }))); -}); - -Deno.test(function permissionQueryReturnsEventTargetSync() { - const status = Deno.permissions.querySync({ name: "hrtime" }); - assert(["granted", "denied", "prompt"].includes(status.state)); - let called = false; - status.addEventListener("change", () => { - called = true; - }); - status.dispatchEvent(new Event("change")); - assert(called); - assert(status === Deno.permissions.querySync({ name: "hrtime" })); -}); - -Deno.test(async function permissionQueryForReadReturnsSameStatus() { - const status1 = await Deno.permissions.query({ - name: "read", - path: ".", - }); - const status2 = await Deno.permissions.query({ - name: "read", - path: ".", - }); - assert(status1 === status2); -}); - -Deno.test(function permissionQueryForReadReturnsSameStatusSync() { - const status1 = Deno.permissions.querySync({ - name: "read", - path: ".", - }); - const status2 = Deno.permissions.querySync({ - name: "read", - path: ".", - }); - assert(status1 === status2); -}); - -Deno.test(function permissionsIllegalConstructor() { - assertThrows(() => new Deno.Permissions(), TypeError, "Illegal constructor."); - assertEquals(Deno.Permissions.length, 0); -}); - -Deno.test(function permissionStatusIllegalConstructor() { - assertThrows( - () => new Deno.PermissionStatus(), - TypeError, - "Illegal constructor.", - ); - assertEquals(Deno.PermissionStatus.length, 0); -}); - -// Regression test for https://github.com/denoland/deno/issues/17020 -Deno.test(async function permissionURL() { - const path = new URL(".", import.meta.url); - - await Deno.permissions.query({ name: "read", path }); - await Deno.permissions.query({ name: "write", path }); - await Deno.permissions.query({ name: "ffi", path }); - await Deno.permissions.query({ name: "run", command: path }); -}); - -Deno.test(function permissionURLSync() { - Deno.permissions.querySync({ - name: "read", - path: new URL(".", import.meta.url), - }); - Deno.permissions.querySync({ - name: "write", - path: new URL(".", import.meta.url), - }); - Deno.permissions.querySync({ - name: "run", - command: new URL(".", import.meta.url), - }); -}); - -Deno.test(async function permissionDescriptorValidation() { - for (const value of [undefined, null, {}]) { - for (const method of ["query", "request", "revoke"]) { - await assertRejects( - async () => { - // deno-lint-ignore no-explicit-any - await (Deno.permissions as any)[method](value as any); - }, - TypeError, - '"undefined" is not a valid permission name', - ); - } - } -}); - -Deno.test(function permissionDescriptorValidationSync() { - for (const value of [undefined, null, {}]) { - for (const method of ["querySync", "revokeSync", "requestSync"]) { - assertThrows( - () => { - // deno-lint-ignore no-explicit-any - (Deno.permissions as any)[method](value as any); - }, - TypeError, - '"undefined" is not a valid permission name', - ); - } - } -}); - -// Regression test for https://github.com/denoland/deno/issues/15894. -Deno.test(async function permissionStatusObjectsNotEqual() { - assert( - await Deno.permissions.query({ name: "env", variable: "A" }) != - await Deno.permissions.query({ name: "env", variable: "B" }), - ); -}); - -Deno.test(function permissionStatusObjectsNotEqualSync() { - assert( - Deno.permissions.querySync({ name: "env", variable: "A" }) != - Deno.permissions.querySync({ name: "env", variable: "B" }), - ); -}); diff --git a/cli/tests/unit/process_test.ts b/cli/tests/unit/process_test.ts deleted file mode 100644 index 0cc4e99aa..000000000 --- a/cli/tests/unit/process_test.ts +++ /dev/null @@ -1,689 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertStrictEquals, - assertStringIncludes, - assertThrows, -} from "./test_util.ts"; - -Deno.test( - { permissions: { read: true, run: false } }, - function runPermissions() { - assertThrows(() => { - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ - cmd: [Deno.execPath(), "eval", "console.log('hello world')"], - }); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runSuccess() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - // freeze the array to ensure it's not modified - cmd: Object.freeze([ - Deno.execPath(), - "eval", - "console.log('hello world')", - ]), - stdout: "piped", - stderr: "null", - }); - const status = await p.status(); - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, undefined); - p.stdout.close(); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runUrl() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - new URL(`file:///${Deno.execPath()}`), - "eval", - "console.log('hello world')", - ], - stdout: "piped", - stderr: "null", - }); - const status = await p.status(); - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, undefined); - p.stdout.close(); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runStdinRid0(): Promise< - void - > { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [Deno.execPath(), "eval", "console.log('hello world')"], - stdin: 0, - stdout: "piped", - stderr: "null", - }); - const status = await p.status(); - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, undefined); - p.stdout.close(); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function runInvalidStdio() { - assertThrows(() => - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ - cmd: [Deno.execPath(), "eval", "console.log('hello world')"], - // @ts-expect-error because Deno.run should throw on invalid stdin. - stdin: "a", - }) - ); - assertThrows(() => - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ - cmd: [Deno.execPath(), "eval", "console.log('hello world')"], - // @ts-expect-error because Deno.run should throw on invalid stdout. - stdout: "b", - }) - ); - assertThrows(() => - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ - cmd: [Deno.execPath(), "eval", "console.log('hello world')"], - // @ts-expect-error because Deno.run should throw on invalid stderr. - stderr: "c", - }) - ); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runCommandFailedWithCode() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [Deno.execPath(), "eval", "Deno.exit(41 + 1)"], - }); - const status = await p.status(); - assertEquals(status.success, false); - assertEquals(status.code, 42); - assertEquals(status.signal, undefined); - p.close(); - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - }, - async function runCommandFailedWithSignal() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "Deno.kill(Deno.pid, 'SIGKILL')", - ], - }); - const status = await p.status(); - assertEquals(status.success, false); - if (Deno.build.os === "windows") { - assertEquals(status.code, 1); - assertEquals(status.signal, undefined); - } else { - assertEquals(status.code, 128 + 9); - assertEquals(status.signal, 9); - } - p.close(); - }, -); - -Deno.test({ permissions: { run: true } }, function runNotFound() { - let error; - try { - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ cmd: ["this file hopefully doesn't exist"] }); - } catch (e) { - error = e; - } - assert(error !== undefined); - assert(error instanceof Deno.errors.NotFound); -}); - -Deno.test( - { permissions: { write: true, run: true, read: true } }, - async function runWithCwdIsAsync() { - const enc = new TextEncoder(); - const cwd = await Deno.makeTempDir({ prefix: "deno_command_test" }); - - const exitCodeFile = "deno_was_here"; - const programFile = "poll_exit.ts"; - const program = ` -async function tryExit() { - try { - const code = parseInt(await Deno.readTextFile("${exitCodeFile}")); - Deno.exit(code); - } catch { - // Retry if we got here before deno wrote the file. - setTimeout(tryExit, 0.01); - } -} - -tryExit(); -`; - - Deno.writeFileSync(`${cwd}/${programFile}`, enc.encode(program)); - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cwd, - cmd: [Deno.execPath(), "run", "--allow-read", programFile], - }); - - // Write the expected exit code *after* starting deno. - // This is how we verify that `run()` is actually asynchronous. - const code = 84; - Deno.writeFileSync(`${cwd}/${exitCodeFile}`, enc.encode(`${code}`)); - - const status = await p.status(); - assertEquals(status.success, false); - assertEquals(status.code, code); - assertEquals(status.signal, undefined); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runStdinPiped(): Promise< - void - > { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "if (new TextDecoder().decode(await Deno.readAll(Deno.stdin)) !== 'hello') throw new Error('Expected \\'hello\\'')", - ], - stdin: "piped", - }); - assert(p.stdin); - assert(!p.stdout); - assert(!p.stderr); - - const msg = new TextEncoder().encode("hello"); - const n = await p.stdin.write(msg); - assertEquals(n, msg.byteLength); - - p.stdin.close(); - - const status = await p.status(); - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, undefined); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runStdoutPiped(): Promise< - void - > { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "await Deno.stdout.write(new TextEncoder().encode('hello'))", - ], - stdout: "piped", - }); - assert(!p.stdin); - assert(!p.stderr); - - const data = new Uint8Array(10); - let r = await p.stdout.read(data); - if (r === null) { - throw new Error("p.stdout.read(...) should not be null"); - } - assertEquals(r, 5); - const s = new TextDecoder().decode(data.subarray(0, r)); - assertEquals(s, "hello"); - r = await p.stdout.read(data); - assertEquals(r, null); - p.stdout.close(); - - const status = await p.status(); - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, undefined); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runStderrPiped(): Promise< - void - > { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "await Deno.stderr.write(new TextEncoder().encode('hello'))", - ], - stderr: "piped", - }); - assert(!p.stdin); - assert(!p.stdout); - - const data = new Uint8Array(10); - let r = await p.stderr.read(data); - if (r === null) { - throw new Error("p.stderr.read should not return null here"); - } - assertEquals(r, 5); - const s = new TextDecoder().decode(data.subarray(0, r)); - assertEquals(s, "hello"); - r = await p.stderr.read(data); - assertEquals(r, null); - p.stderr!.close(); - - const status = await p.status(); - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, undefined); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runOutput() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "await Deno.stdout.write(new TextEncoder().encode('hello'))", - ], - stdout: "piped", - }); - const output = await p.output(); - const s = new TextDecoder().decode(output); - assertEquals(s, "hello"); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runStderrOutput(): Promise< - void - > { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "await Deno.stderr.write(new TextEncoder().encode('error'))", - ], - stderr: "piped", - }); - const error = await p.stderrOutput(); - const s = new TextDecoder().decode(error); - assertEquals(s, "error"); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, write: true, read: true } }, - async function runRedirectStdoutStderr() { - const tempDir = await Deno.makeTempDir(); - const fileName = tempDir + "/redirected_stdio.txt"; - using file = await Deno.open(fileName, { - create: true, - write: true, - }); - - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "Deno.stderr.write(new TextEncoder().encode('error\\n')); Deno.stdout.write(new TextEncoder().encode('output\\n'));", - ], - stdout: file.rid, - stderr: file.rid, - }); - - await p.status(); - p.close(); - - const fileContents = await Deno.readFile(fileName); - const decoder = new TextDecoder(); - const text = decoder.decode(fileContents); - - assertStringIncludes(text, "error"); - assertStringIncludes(text, "output"); - }, -); - -Deno.test( - { permissions: { run: true, write: true, read: true } }, - async function runRedirectStdin() { - const tempDir = await Deno.makeTempDir(); - const fileName = tempDir + "/redirected_stdio.txt"; - await Deno.writeTextFile(fileName, "hello"); - using file = await Deno.open(fileName); - - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "if (new TextDecoder().decode(await Deno.readAll(Deno.stdin)) !== 'hello') throw new Error('Expected \\'hello\\'')", - ], - stdin: file.rid, - }); - - const status = await p.status(); - assertEquals(status.code, 0); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runEnv() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", - ], - env: { - FOO: "0123", - BAR: "4567", - }, - stdout: "piped", - }); - const output = await p.output(); - const s = new TextDecoder().decode(output); - assertEquals(s, "01234567"); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runClose() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "setTimeout(() => Deno.stdout.write(new TextEncoder().encode('error')), 10000)", - ], - stderr: "piped", - }); - assert(!p.stdin); - assert(!p.stdout); - - p.close(); - - const data = new Uint8Array(10); - const r = await p.stderr.read(data); - assertEquals(r, null); - p.stderr.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runKillAfterStatus() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [Deno.execPath(), "eval", 'console.log("hello")'], - }); - await p.status(); - - let error = null; - try { - p.kill("SIGTERM"); - } catch (e) { - error = e; - } - - assert( - error instanceof Deno.errors.NotFound || - // On Windows, the underlying Windows API may return - // `ERROR_ACCESS_DENIED` when the process has exited, but hasn't been - // completely cleaned up yet and its `pid` is still valid. - (Deno.build.os === "windows" && - error instanceof Deno.errors.PermissionDenied), - ); - - p.close(); - }, -); - -Deno.test({ permissions: { run: false } }, function killPermissions() { - assertThrows(() => { - // Unlike the other test cases, we don't have permission to spawn a - // subprocess we can safely kill. Instead we send SIGCONT to the current - // process - assuming that Deno does not have a special handler set for it - // and will just continue even if a signal is erroneously sent. - Deno.kill(Deno.pid, "SIGCONT"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { ignore: Deno.build.os !== "windows", permissions: { run: true } }, - function negativePidInvalidWindows() { - assertThrows(() => { - Deno.kill(-1, "SIGTERM"); - }, TypeError); - }, -); - -Deno.test( - { ignore: Deno.build.os !== "windows", permissions: { run: true } }, - function invalidSignalNameWindows() { - assertThrows(() => { - Deno.kill(Deno.pid, "SIGUSR1"); - }, TypeError); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function killSuccess() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [Deno.execPath(), "eval", "setTimeout(() => {}, 10000)"], - }); - - try { - Deno.kill(p.pid, "SIGKILL"); - const status = await p.status(); - - assertEquals(status.success, false); - if (Deno.build.os === "windows") { - assertEquals(status.code, 1); - assertEquals(status.signal, undefined); - } else { - assertEquals(status.code, 137); - assertEquals(status.signal, 9); - } - } finally { - p.close(); - } - }, -); - -Deno.test({ permissions: { run: true, read: true } }, function killFailed() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [Deno.execPath(), "eval", "setTimeout(() => {}, 10000)"], - }); - assert(!p.stdin); - assert(!p.stdout); - - assertThrows(() => { - // @ts-expect-error testing runtime error of bad signal - Deno.kill(p.pid, "foobar"); - }, TypeError); - - p.close(); -}); - -Deno.test( - { permissions: { run: true, read: true, env: true } }, - async function clearEnv(): Promise<void> { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "-p", - "JSON.stringify(Deno.env.toObject())", - ], - stdout: "piped", - clearEnv: true, - env: { - FOO: "23147", - }, - }); - - const obj = JSON.parse(new TextDecoder().decode(await p.output())); - - // can't check for object equality because the OS may set additional env - // vars for processes, so we check if PATH isn't present as that is a common - // env var across OS's and isn't set for processes. - assertEquals(obj.FOO, "23147"); - assert(!("PATH" in obj)); - - p.close(); - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - async function uid(): Promise<void> { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - "id", - "-u", - ], - stdout: "piped", - }); - - const currentUid = new TextDecoder().decode(await p.output()); - p.close(); - - if (currentUid !== "0") { - assertThrows(() => { - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ - cmd: [ - "echo", - "fhqwhgads", - ], - uid: 0, - }); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - async function gid(): Promise<void> { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - "id", - "-g", - ], - stdout: "piped", - }); - - const currentGid = new TextDecoder().decode(await p.output()); - p.close(); - - if (currentGid !== "0") { - assertThrows(() => { - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ - cmd: [ - "echo", - "fhqwhgads", - ], - gid: 0, - }); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true, write: true }, - ignore: Deno.build.os === "windows", - }, - async function non_existent_cwd(): Promise<void> { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - `const dir = Deno.makeTempDirSync(); - Deno.chdir(dir); - Deno.removeSync(dir); - const p = Deno.run({cmd:[Deno.execPath(), "eval", "console.log(1);"]}); - const { code } = await p.status(); - p.close(); - Deno.exit(code); - `, - ], - stdout: "piped", - stderr: "piped", - }); - - const { code } = await p.status(); - const stderr = new TextDecoder().decode(await p.stderrOutput()); - p.close(); - p.stdout.close(); - assertStrictEquals(code, 1); - assertStringIncludes(stderr, "Failed getting cwd."); - }, -); diff --git a/cli/tests/unit/progressevent_test.ts b/cli/tests/unit/progressevent_test.ts deleted file mode 100644 index 809c2ad39..000000000 --- a/cli/tests/unit/progressevent_test.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "./test_util.ts"; - -Deno.test(function progressEventConstruct() { - const progressEventDefs = new ProgressEvent("progressEventType", {}); - assertEquals(progressEventDefs.lengthComputable, false); - assertEquals(progressEventDefs.loaded, 0); - assertEquals(progressEventDefs.total, 0); - - const progressEvent = new ProgressEvent("progressEventType", { - lengthComputable: true, - loaded: 123, - total: 456, - }); - assertEquals(progressEvent.lengthComputable, true); - assertEquals(progressEvent.loaded, 123); - assertEquals(progressEvent.total, 456); -}); diff --git a/cli/tests/unit/promise_hooks_test.ts b/cli/tests/unit/promise_hooks_test.ts deleted file mode 100644 index f7c44155d..000000000 --- a/cli/tests/unit/promise_hooks_test.ts +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assertEquals } from "./test_util.ts"; - -function monitorPromises(outputArray: string[]) { - const promiseIds = new Map<Promise<unknown>, string>(); - - function identify(promise: Promise<unknown>) { - if (!promiseIds.has(promise)) { - promiseIds.set(promise, "p" + (promiseIds.size + 1)); - } - return promiseIds.get(promise); - } - - // @ts-ignore: Deno[Deno.internal].core allowed - Deno[Deno.internal].core.setPromiseHooks( - (promise: Promise<unknown>, parentPromise?: Promise<unknown>) => { - outputArray.push( - `init ${identify(promise)}` + - (parentPromise ? ` from ${identify(parentPromise)}` : ``), - ); - }, - (promise: Promise<unknown>) => { - outputArray.push(`before ${identify(promise)}`); - }, - (promise: Promise<unknown>) => { - outputArray.push(`after ${identify(promise)}`); - }, - (promise: Promise<unknown>) => { - outputArray.push(`resolve ${identify(promise)}`); - }, - ); -} - -Deno.test(async function promiseHookBasic() { - // Bogus await here to ensure any pending promise resolution from the - // test runtime has a chance to run and avoid contaminating our results. - await Promise.resolve(null); - - const hookResults: string[] = []; - monitorPromises(hookResults); - - async function asyncFn() { - await Promise.resolve(15); - await Promise.resolve(20); - Promise.reject(new Error()).catch(() => {}); - } - - // The function above is equivalent to: - // function asyncFn() { - // return new Promise(resolve => { - // Promise.resolve(15).then(() => { - // Promise.resolve(20).then(() => { - // Promise.reject(new Error()).catch(() => {}); - // resolve(); - // }); - // }); - // }); - // } - - await asyncFn(); - - assertEquals(hookResults, [ - "init p1", // Creates the promise representing the return of `asyncFn()`. - "init p2", // Creates the promise representing `Promise.resolve(15)`. - "resolve p2", // The previous promise resolves to `15` immediately. - "init p3 from p2", // Creates the promise that is resolved after the first `await` of the function. Equivalent to `p2.then(...)`. - "init p4 from p1", // The resolution above gives time for other pending code to run. Creates the promise that is resolved - // from the `await` at `await asyncFn()`, the last code to run. Equivalent to `asyncFn().then(...)`. - "before p3", // Begins executing the code after `await Promise.resolve(15)`. - "init p5", // Creates the promise representing `Promise.resolve(20)`. - "resolve p5", // The previous promise resolves to `20` immediately. - "init p6 from p5", // Creates the promise that is resolved after the second `await` of the function. Equivalent to `p5.then(...)`. - "resolve p3", // The promise representing the code right after the first await is marked as resolved. - "after p3", // We are now after the resolution code of the promise above. - "before p6", // Begins executing the code after `await Promise.resolve(20)`. - "init p7", // Creates a new promise representing `Promise.reject(new Error())`. - "resolve p7", // This promise is "resolved" immediately to a rejection with an error instance. - "init p8 from p7", // Creates a new promise for the `.catch` of the previous promise. - "resolve p1", // At this point the promise of the function is resolved. - "resolve p6", // This concludes the resolution of the code after `await Promise.resolve(20)`. - "after p6", // We are now after the resolution code of the promise above. - "before p8", // The `.catch` block is pending execution, it begins to execute. - "resolve p8", // It does nothing and resolves to `undefined`. - "after p8", // We are after the resolution of the `.catch` block. - "before p4", // Now we begin the execution of the code that happens after `await asyncFn();`. - ]); -}); - -Deno.test(async function promiseHookMultipleConsumers() { - const hookResultsFirstConsumer: string[] = []; - const hookResultsSecondConsumer: string[] = []; - - monitorPromises(hookResultsFirstConsumer); - monitorPromises(hookResultsSecondConsumer); - - async function asyncFn() { - await Promise.resolve(15); - await Promise.resolve(20); - Promise.reject(new Error()).catch(() => {}); - } - await asyncFn(); - - // Two invocations of `setPromiseHooks` should yield the exact same results, in the same order. - assertEquals( - hookResultsFirstConsumer, - hookResultsSecondConsumer, - ); -}); diff --git a/cli/tests/unit/read_dir_test.ts b/cli/tests/unit/read_dir_test.ts deleted file mode 100644 index 50447ef6a..000000000 --- a/cli/tests/unit/read_dir_test.ts +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertRejects, - assertThrows, - pathToAbsoluteFileUrl, -} from "./test_util.ts"; - -function assertSameContent(files: Deno.DirEntry[]) { - let counter = 0; - - for (const entry of files) { - if (entry.name === "subdir") { - assert(entry.isDirectory); - counter++; - } - } - - assertEquals(counter, 1); -} - -Deno.test({ permissions: { read: true } }, function readDirSyncSuccess() { - const files = [...Deno.readDirSync("cli/tests/testdata")]; - assertSameContent(files); -}); - -Deno.test({ permissions: { read: true } }, function readDirSyncWithUrl() { - const files = [ - ...Deno.readDirSync(pathToAbsoluteFileUrl("cli/tests/testdata")), - ]; - assertSameContent(files); -}); - -Deno.test({ permissions: { read: false } }, function readDirSyncPerm() { - assertThrows(() => { - Deno.readDirSync("tests/"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, function readDirSyncNotDir() { - assertThrows( - () => { - Deno.readDirSync("cli/tests/testdata/assets/fixture.json"); - }, - Error, - `readdir 'cli/tests/testdata/assets/fixture.json'`, - ); -}); - -Deno.test({ permissions: { read: true } }, function readDirSyncNotFound() { - assertThrows( - () => { - Deno.readDirSync("bad_dir_name"); - }, - Deno.errors.NotFound, - `readdir 'bad_dir_name'`, - ); -}); - -Deno.test({ permissions: { read: true } }, async function readDirSuccess() { - const files = []; - for await (const dirEntry of Deno.readDir("cli/tests/testdata")) { - files.push(dirEntry); - } - assertSameContent(files); -}); - -Deno.test({ permissions: { read: true } }, async function readDirWithUrl() { - const files = []; - for await ( - const dirEntry of Deno.readDir(pathToAbsoluteFileUrl("cli/tests/testdata")) - ) { - files.push(dirEntry); - } - assertSameContent(files); -}); - -Deno.test({ permissions: { read: false } }, async function readDirPerm() { - await assertRejects(async () => { - await Deno.readDir("tests/")[Symbol.asyncIterator]().next(); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { permissions: { read: true }, ignore: Deno.build.os == "windows" }, - async function readDirDevFd(): Promise< - void - > { - for await (const _ of Deno.readDir("/dev/fd")) { - // We don't actually care whats in here; just that we don't panic on non regular entries - } - }, -); - -Deno.test( - { permissions: { read: true }, ignore: Deno.build.os == "windows" }, - function readDirDevFdSync() { - for (const _ of Deno.readDirSync("/dev/fd")) { - // We don't actually care whats in here; just that we don't panic on non regular file entries - } - }, -); - -Deno.test({ permissions: { read: true } }, async function readDirNotFound() { - await assertRejects( - async () => { - await Deno.readDir("bad_dir_name")[Symbol.asyncIterator]().next(); - }, - Deno.errors.NotFound, - `readdir 'bad_dir_name'`, - ); -}); diff --git a/cli/tests/unit/read_file_test.ts b/cli/tests/unit/read_file_test.ts deleted file mode 100644 index 24ec1aedc..000000000 --- a/cli/tests/unit/read_file_test.ts +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertRejects, - assertThrows, - pathToAbsoluteFileUrl, - unreachable, -} from "./test_util.ts"; - -Deno.test({ permissions: { read: true } }, function readFileSyncSuccess() { - const data = Deno.readFileSync("cli/tests/testdata/assets/fixture.json"); - assert(data.byteLength > 0); - const decoder = new TextDecoder("utf-8"); - const json = decoder.decode(data); - const pkg = JSON.parse(json); - assertEquals(pkg.name, "deno"); -}); - -Deno.test({ permissions: { read: true } }, function readFileSyncUrl() { - const data = Deno.readFileSync( - pathToAbsoluteFileUrl("cli/tests/testdata/assets/fixture.json"), - ); - assert(data.byteLength > 0); - const decoder = new TextDecoder("utf-8"); - const json = decoder.decode(data); - const pkg = JSON.parse(json); - assertEquals(pkg.name, "deno"); -}); - -Deno.test({ permissions: { read: false } }, function readFileSyncPerm() { - assertThrows(() => { - Deno.readFileSync("cli/tests/testdata/assets/fixture.json"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, function readFileSyncNotFound() { - assertThrows(() => { - Deno.readFileSync("bad_filename"); - }, Deno.errors.NotFound); -}); - -Deno.test({ permissions: { read: true } }, async function readFileUrl() { - const data = await Deno.readFile( - pathToAbsoluteFileUrl("cli/tests/testdata/assets/fixture.json"), - ); - assert(data.byteLength > 0); - const decoder = new TextDecoder("utf-8"); - const json = decoder.decode(data); - const pkg = JSON.parse(json); - assertEquals(pkg.name, "deno"); -}); - -Deno.test({ permissions: { read: true } }, async function readFileSuccess() { - const data = await Deno.readFile("cli/tests/testdata/assets/fixture.json"); - assert(data.byteLength > 0); - const decoder = new TextDecoder("utf-8"); - const json = decoder.decode(data); - const pkg = JSON.parse(json); - assertEquals(pkg.name, "deno"); -}); - -Deno.test({ permissions: { read: false } }, async function readFilePerm() { - await assertRejects(async () => { - await Deno.readFile("cli/tests/testdata/assets/fixture.json"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, function readFileSyncLoop() { - for (let i = 0; i < 256; i++) { - Deno.readFileSync("cli/tests/testdata/assets/fixture.json"); - } -}); - -Deno.test( - { permissions: { read: true } }, - async function readFileDoesNotLeakResources() { - await assertRejects(async () => await Deno.readFile("cli")); - }, -); - -Deno.test( - { permissions: { read: true } }, - function readFileSyncDoesNotLeakResources() { - assertThrows(() => Deno.readFileSync("cli")); - }, -); - -Deno.test( - { permissions: { read: true } }, - async function readFileWithAbortSignal() { - const ac = new AbortController(); - queueMicrotask(() => ac.abort()); - const error = await assertRejects( - async () => { - await Deno.readFile("cli/tests/testdata/assets/fixture.json", { - signal: ac.signal, - }); - }, - ); - assert(error instanceof DOMException); - assertEquals(error.name, "AbortError"); - }, -); - -Deno.test( - { permissions: { read: true } }, - async function readFileWithAbortSignalReason() { - const ac = new AbortController(); - const abortReason = new Error(); - queueMicrotask(() => ac.abort(abortReason)); - const error = await assertRejects( - async () => { - await Deno.readFile("cli/tests/testdata/assets/fixture.json", { - signal: ac.signal, - }); - }, - ); - assertEquals(error, abortReason); - }, -); - -Deno.test( - { permissions: { read: true } }, - async function readFileWithAbortSignalPrimitiveReason() { - const ac = new AbortController(); - queueMicrotask(() => ac.abort("Some string")); - try { - await Deno.readFile("cli/tests/testdata/assets/fixture.json", { - signal: ac.signal, - }); - unreachable(); - } catch (e) { - assertEquals(e, "Some string"); - } - }, -); - -// Test that AbortController's cancel handle is cleaned-up correctly, and do not leak resources. -Deno.test( - { permissions: { read: true } }, - async function readFileWithAbortSignalNotCalled() { - const ac = new AbortController(); - await Deno.readFile("cli/tests/testdata/assets/fixture.json", { - signal: ac.signal, - }); - }, -); - -Deno.test( - { permissions: { read: true }, ignore: Deno.build.os !== "linux" }, - async function readFileProcFs() { - const data = await Deno.readFile("/proc/self/stat"); - assert(data.byteLength > 0); - }, -); - -Deno.test( - { permissions: { read: true } }, - async function readFileNotFoundErrorCode() { - try { - await Deno.readFile("definitely-not-found.json"); - } catch (e) { - assertEquals(e.code, "ENOENT"); - } - }, -); - -Deno.test( - { permissions: { read: true } }, - async function readFileIsDirectoryErrorCode() { - try { - await Deno.readFile("cli/tests/testdata/assets/"); - } catch (e) { - if (Deno.build.os === "windows") { - assertEquals(e.code, "ENOENT"); - } else { - assertEquals(e.code, "EISDIR"); - } - } - }, -); diff --git a/cli/tests/unit/read_link_test.ts b/cli/tests/unit/read_link_test.ts deleted file mode 100644 index 3ed1817bb..000000000 --- a/cli/tests/unit/read_link_test.ts +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assertEquals, - assertRejects, - assertThrows, - pathToAbsoluteFileUrl, -} from "./test_util.ts"; - -Deno.test( - { permissions: { write: true, read: true } }, - function readLinkSyncSuccess() { - const testDir = Deno.makeTempDirSync(); - const target = testDir + - (Deno.build.os == "windows" ? "\\target" : "/target"); - const symlink = testDir + - (Deno.build.os == "windows" ? "\\symlink" : "/symlink"); - Deno.mkdirSync(target); - Deno.symlinkSync(target, symlink); - const targetPath = Deno.readLinkSync(symlink); - assertEquals(targetPath, target); - }, -); - -Deno.test( - { permissions: { write: true, read: true } }, - function readLinkSyncUrlSuccess() { - const testDir = Deno.makeTempDirSync(); - const target = testDir + - (Deno.build.os == "windows" ? "\\target" : "/target"); - const symlink = testDir + - (Deno.build.os == "windows" ? "\\symlink" : "/symlink"); - Deno.mkdirSync(target); - Deno.symlinkSync(target, symlink); - const targetPath = Deno.readLinkSync(pathToAbsoluteFileUrl(symlink)); - assertEquals(targetPath, target); - }, -); - -Deno.test({ permissions: { read: false } }, function readLinkSyncPerm() { - assertThrows(() => { - Deno.readLinkSync("/symlink"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, function readLinkSyncNotFound() { - assertThrows( - () => { - Deno.readLinkSync("bad_filename"); - }, - Deno.errors.NotFound, - `readlink 'bad_filename'`, - ); -}); - -Deno.test( - { permissions: { write: true, read: true } }, - async function readLinkSuccess() { - const testDir = Deno.makeTempDirSync(); - const target = testDir + - (Deno.build.os == "windows" ? "\\target" : "/target"); - const symlink = testDir + - (Deno.build.os == "windows" ? "\\symlink" : "/symlink"); - Deno.mkdirSync(target); - Deno.symlinkSync(target, symlink); - const targetPath = await Deno.readLink(symlink); - assertEquals(targetPath, target); - }, -); - -Deno.test( - { permissions: { write: true, read: true } }, - async function readLinkUrlSuccess() { - const testDir = Deno.makeTempDirSync(); - const target = testDir + - (Deno.build.os == "windows" ? "\\target" : "/target"); - const symlink = testDir + - (Deno.build.os == "windows" ? "\\symlink" : "/symlink"); - Deno.mkdirSync(target); - Deno.symlinkSync(target, symlink); - const targetPath = await Deno.readLink(pathToAbsoluteFileUrl(symlink)); - assertEquals(targetPath, target); - }, -); - -Deno.test({ permissions: { read: false } }, async function readLinkPerm() { - await assertRejects(async () => { - await Deno.readLink("/symlink"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, async function readLinkNotFound() { - await assertRejects( - async () => { - await Deno.readLink("bad_filename"); - }, - Deno.errors.NotFound, - `readlink 'bad_filename'`, - ); -}); diff --git a/cli/tests/unit/read_text_file_test.ts b/cli/tests/unit/read_text_file_test.ts deleted file mode 100644 index 5a64522af..000000000 --- a/cli/tests/unit/read_text_file_test.ts +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { - assert, - assertEquals, - assertRejects, - assertThrows, - pathToAbsoluteFileUrl, - unreachable, -} from "./test_util.ts"; - -Deno.test({ permissions: { read: true } }, function readTextFileSyncSuccess() { - const data = Deno.readTextFileSync("cli/tests/testdata/assets/fixture.json"); - assert(data.length > 0); - const pkg = JSON.parse(data); - assertEquals(pkg.name, "deno"); -}); - -Deno.test({ permissions: { read: true } }, function readTextFileSyncByUrl() { - const data = Deno.readTextFileSync( - pathToAbsoluteFileUrl("cli/tests/testdata/assets/fixture.json"), - ); - assert(data.length > 0); - const pkg = JSON.parse(data); - assertEquals(pkg.name, "deno"); -}); - -Deno.test({ permissions: { read: false } }, function readTextFileSyncPerm() { - assertThrows(() => { - Deno.readTextFileSync("cli/tests/testdata/assets/fixture.json"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, function readTextFileSyncNotFound() { - assertThrows(() => { - Deno.readTextFileSync("bad_filename"); - }, Deno.errors.NotFound); -}); - -Deno.test( - { permissions: { read: true } }, - async function readTextFileSuccess() { - const data = await Deno.readTextFile( - "cli/tests/testdata/assets/fixture.json", - ); - assert(data.length > 0); - const pkg = JSON.parse(data); - assertEquals(pkg.name, "deno"); - }, -); - -Deno.test({ permissions: { read: true } }, async function readTextFileByUrl() { - const data = await Deno.readTextFile( - pathToAbsoluteFileUrl("cli/tests/testdata/assets/fixture.json"), - ); - assert(data.length > 0); - const pkg = JSON.parse(data); - assertEquals(pkg.name, "deno"); -}); - -Deno.test({ permissions: { read: false } }, async function readTextFilePerm() { - await assertRejects(async () => { - await Deno.readTextFile("cli/tests/testdata/assets/fixture.json"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, function readTextFileSyncLoop() { - for (let i = 0; i < 256; i++) { - Deno.readTextFileSync("cli/tests/testdata/assets/fixture.json"); - } -}); - -Deno.test( - { permissions: { read: true } }, - async function readTextFileDoesNotLeakResources() { - await assertRejects(async () => await Deno.readTextFile("cli")); - }, -); - -Deno.test( - { permissions: { read: true } }, - function readTextFileSyncDoesNotLeakResources() { - assertThrows(() => Deno.readTextFileSync("cli")); - }, -); - -Deno.test( - { permissions: { read: true } }, - async function readTextFileWithAbortSignal() { - const ac = new AbortController(); - queueMicrotask(() => ac.abort()); - const error = await assertRejects( - async () => { - await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", { - signal: ac.signal, - }); - }, - ); - assert(error instanceof DOMException); - assertEquals(error.name, "AbortError"); - }, -); - -Deno.test( - { permissions: { read: true } }, - async function readTextFileWithAbortSignalReason() { - const ac = new AbortController(); - const abortReason = new Error(); - queueMicrotask(() => ac.abort(abortReason)); - const error = await assertRejects( - async () => { - await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", { - signal: ac.signal, - }); - }, - ); - assertEquals(error, abortReason); - }, -); - -Deno.test( - { permissions: { read: true } }, - async function readTextFileWithAbortSignalPrimitiveReason() { - const ac = new AbortController(); - queueMicrotask(() => ac.abort("Some string")); - try { - await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", { - signal: ac.signal, - }); - unreachable(); - } catch (e) { - assertEquals(e, "Some string"); - } - }, -); - -// Test that AbortController's cancel handle is cleaned-up correctly, and do not leak resources. -Deno.test( - { permissions: { read: true } }, - async function readTextFileWithAbortSignalNotCalled() { - const ac = new AbortController(); - await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", { - signal: ac.signal, - }); - }, -); - -Deno.test( - { permissions: { read: true }, ignore: Deno.build.os !== "linux" }, - async function readTextFileProcFs() { - const data = await Deno.readTextFile("/proc/self/stat"); - assert(data.length > 0); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function readTextFileSyncV8LimitError() { - const kStringMaxLengthPlusOne = 536870888 + 1; - const bytes = new Uint8Array(kStringMaxLengthPlusOne); - const filePath = "cli/tests/testdata/too_big_a_file.txt"; - - try { - Deno.writeFileSync(filePath, bytes); - } catch { - // NOTE(bartlomieju): writing a 0.5Gb file might be too much for CI, - // so skip running if writing fails. - return; - } - - assertThrows( - () => { - Deno.readTextFileSync(filePath); - }, - TypeError, - "buffer exceeds maximum length", - ); - - Deno.removeSync(filePath); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function readTextFileV8LimitError() { - const kStringMaxLengthPlusOne = 536870888 + 1; - const bytes = new Uint8Array(kStringMaxLengthPlusOne); - const filePath = "cli/tests/testdata/too_big_a_file_2.txt"; - - try { - await Deno.writeFile(filePath, bytes); - } catch { - // NOTE(bartlomieju): writing a 0.5Gb file might be too much for CI, - // so skip running if writing fails. - return; - } - - await assertRejects( - async () => { - await Deno.readTextFile(filePath); - }, - TypeError, - "buffer exceeds maximum length", - ); - - await Deno.remove(filePath); - }, -); diff --git a/cli/tests/unit/real_path_test.ts b/cli/tests/unit/real_path_test.ts deleted file mode 100644 index 1b944f5ad..000000000 --- a/cli/tests/unit/real_path_test.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertMatch, - assertRejects, - assertThrows, - pathToAbsoluteFileUrl, -} from "./test_util.ts"; - -Deno.test({ permissions: { read: true } }, function realPathSyncSuccess() { - const relative = "cli/tests/testdata/assets/fixture.json"; - const realPath = Deno.realPathSync(relative); - if (Deno.build.os !== "windows") { - assert(realPath.startsWith("/")); - assert(realPath.endsWith(relative)); - } else { - assertMatch(realPath, /^[A-Z]:\\/); - assert(realPath.endsWith(relative.replace(/\//g, "\\"))); - } -}); - -Deno.test({ permissions: { read: true } }, function realPathSyncUrl() { - const relative = "cli/tests/testdata/assets/fixture.json"; - const url = pathToAbsoluteFileUrl(relative); - assertEquals(Deno.realPathSync(relative), Deno.realPathSync(url)); -}); - -Deno.test( - { - permissions: { read: true, write: true }, - }, - function realPathSyncSymlink() { - const testDir = Deno.makeTempDirSync(); - const target = testDir + "/target"; - const symlink = testDir + "/symln"; - Deno.mkdirSync(target); - Deno.symlinkSync(target, symlink); - const realPath = Deno.realPathSync(symlink); - if (Deno.build.os !== "windows") { - assert(realPath.startsWith("/")); - assert(realPath.endsWith("/target")); - } else { - assertMatch(realPath, /^[A-Z]:\\/); - assert(realPath.endsWith("\\target")); - } - }, -); - -Deno.test({ permissions: { read: false } }, function realPathSyncPerm() { - assertThrows(() => { - Deno.realPathSync("some_file"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, function realPathSyncNotFound() { - assertThrows(() => { - Deno.realPathSync("bad_filename"); - }, Deno.errors.NotFound); -}); - -Deno.test({ permissions: { read: true } }, async function realPathSuccess() { - const relativePath = "cli/tests/testdata/assets/fixture.json"; - const realPath = await Deno.realPath(relativePath); - if (Deno.build.os !== "windows") { - assert(realPath.startsWith("/")); - assert(realPath.endsWith(relativePath)); - } else { - assertMatch(realPath, /^[A-Z]:\\/); - assert(realPath.endsWith(relativePath.replace(/\//g, "\\"))); - } -}); - -Deno.test( - { permissions: { read: true } }, - async function realPathUrl() { - const relative = "cli/tests/testdata/assets/fixture.json"; - const url = pathToAbsoluteFileUrl(relative); - assertEquals(await Deno.realPath(relative), await Deno.realPath(url)); - }, -); - -Deno.test( - { - permissions: { read: true, write: true }, - }, - async function realPathSymlink() { - const testDir = Deno.makeTempDirSync(); - const target = testDir + "/target"; - const symlink = testDir + "/symln"; - Deno.mkdirSync(target); - Deno.symlinkSync(target, symlink); - const realPath = await Deno.realPath(symlink); - if (Deno.build.os !== "windows") { - assert(realPath.startsWith("/")); - assert(realPath.endsWith("/target")); - } else { - assertMatch(realPath, /^[A-Z]:\\/); - assert(realPath.endsWith("\\target")); - } - }, -); - -Deno.test({ permissions: { read: false } }, async function realPathPerm() { - await assertRejects(async () => { - await Deno.realPath("some_file"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, async function realPathNotFound() { - await assertRejects(async () => { - await Deno.realPath("bad_filename"); - }, Deno.errors.NotFound); -}); diff --git a/cli/tests/unit/ref_unref_test.ts b/cli/tests/unit/ref_unref_test.ts deleted file mode 100644 index 6f5bcf0a7..000000000 --- a/cli/tests/unit/ref_unref_test.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assertNotEquals, execCode } from "./test_util.ts"; - -Deno.test("[unrefOpPromise] unref'ing invalid ops does not have effects", async () => { - const [statusCode, _] = await execCode(` - Deno[Deno.internal].core.unrefOpPromise(new Promise(r => null)); - setTimeout(() => { throw new Error() }, 10) - `); - // Invalid unrefOpPromise call doesn't affect exit condition of event loop - assertNotEquals(statusCode, 0); -}); diff --git a/cli/tests/unit/remove_test.ts b/cli/tests/unit/remove_test.ts deleted file mode 100644 index f4e54dc52..000000000 --- a/cli/tests/unit/remove_test.ts +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert, assertRejects, assertThrows } from "./test_util.ts"; - -const REMOVE_METHODS = ["remove", "removeSync"] as const; - -Deno.test( - { permissions: { write: true, read: true } }, - async function removeDirSuccess() { - for (const method of REMOVE_METHODS) { - // REMOVE EMPTY DIRECTORY - const path = Deno.makeTempDirSync() + "/subdir"; - Deno.mkdirSync(path); - const pathInfo = Deno.statSync(path); - assert(pathInfo.isDirectory); // check exist first - await Deno[method](path); // remove - // We then check again after remove - assertThrows(() => { - Deno.statSync(path); - }, Deno.errors.NotFound); - } - }, -); - -Deno.test( - { permissions: { write: true, read: true } }, - async function removeFileSuccess() { - for (const method of REMOVE_METHODS) { - // REMOVE FILE - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - Deno.writeFileSync(filename, data, { mode: 0o666 }); - const fileInfo = Deno.statSync(filename); - assert(fileInfo.isFile); // check exist first - await Deno[method](filename); // remove - // We then check again after remove - assertThrows(() => { - Deno.statSync(filename); - }, Deno.errors.NotFound); - } - }, -); - -Deno.test( - { permissions: { write: true, read: true } }, - async function removeFileByUrl() { - for (const method of REMOVE_METHODS) { - // REMOVE FILE - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - - const tempDir = Deno.makeTempDirSync(); - const fileUrl = new URL( - `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`, - ); - - Deno.writeFileSync(fileUrl, data, { mode: 0o666 }); - const fileInfo = Deno.statSync(fileUrl); - assert(fileInfo.isFile); // check exist first - await Deno[method](fileUrl); // remove - // We then check again after remove - assertThrows(() => { - Deno.statSync(fileUrl); - }, Deno.errors.NotFound); - } - }, -); - -Deno.test( - { permissions: { write: true, read: true } }, - async function removeFail() { - for (const method of REMOVE_METHODS) { - // NON-EMPTY DIRECTORY - const path = Deno.makeTempDirSync() + "/dir/subdir"; - const subPath = path + "/subsubdir"; - Deno.mkdirSync(path, { recursive: true }); - Deno.mkdirSync(subPath); - const pathInfo = Deno.statSync(path); - assert(pathInfo.isDirectory); // check exist first - const subPathInfo = Deno.statSync(subPath); - assert(subPathInfo.isDirectory); // check exist first - - await assertRejects( - async () => { - await Deno[method](path); - }, - Error, - `remove '${path}'`, - ); - // TODO(ry) Is Other really the error we should get here? What would Go do? - - // NON-EXISTENT DIRECTORY/FILE - await assertRejects( - async () => { - await Deno[method]("/baddir"); - }, - Deno.errors.NotFound, - `remove '/baddir'`, - ); - } - }, -); - -Deno.test( - { permissions: { write: true, read: true } }, - async function removeDanglingSymlinkSuccess() { - for (const method of REMOVE_METHODS) { - const danglingSymlinkPath = Deno.makeTempDirSync() + "/dangling_symlink"; - if (Deno.build.os === "windows") { - Deno.symlinkSync("unexistent_file", danglingSymlinkPath, { - type: "file", - }); - } else { - Deno.symlinkSync("unexistent_file", danglingSymlinkPath); - } - const pathInfo = Deno.lstatSync(danglingSymlinkPath); - assert(pathInfo.isSymlink); - await Deno[method](danglingSymlinkPath); - assertThrows(() => { - Deno.lstatSync(danglingSymlinkPath); - }, Deno.errors.NotFound); - } - }, -); - -Deno.test( - { permissions: { write: true, read: true } }, - async function removeValidSymlinkSuccess() { - for (const method of REMOVE_METHODS) { - const encoder = new TextEncoder(); - const data = encoder.encode("Test"); - const tempDir = Deno.makeTempDirSync(); - const filePath = tempDir + "/test.txt"; - const validSymlinkPath = tempDir + "/valid_symlink"; - Deno.writeFileSync(filePath, data, { mode: 0o666 }); - if (Deno.build.os === "windows") { - Deno.symlinkSync(filePath, validSymlinkPath, { type: "file" }); - } else { - Deno.symlinkSync(filePath, validSymlinkPath); - } - const symlinkPathInfo = Deno.statSync(validSymlinkPath); - assert(symlinkPathInfo.isFile); - await Deno[method](validSymlinkPath); - assertThrows(() => { - Deno.statSync(validSymlinkPath); - }, Deno.errors.NotFound); - await Deno[method](filePath); - } - }, -); - -Deno.test({ permissions: { write: false } }, async function removePerm() { - for (const method of REMOVE_METHODS) { - await assertRejects(async () => { - await Deno[method]("/baddir"); - }, Deno.errors.PermissionDenied); - } -}); - -Deno.test( - { permissions: { write: true, read: true } }, - async function removeAllDirSuccess() { - for (const method of REMOVE_METHODS) { - // REMOVE EMPTY DIRECTORY - let path = Deno.makeTempDirSync() + "/dir/subdir"; - Deno.mkdirSync(path, { recursive: true }); - let pathInfo = Deno.statSync(path); - assert(pathInfo.isDirectory); // check exist first - await Deno[method](path, { recursive: true }); // remove - // We then check again after remove - assertThrows( - () => { - Deno.statSync(path); - }, // Directory is gone - Deno.errors.NotFound, - ); - - // REMOVE NON-EMPTY DIRECTORY - path = Deno.makeTempDirSync() + "/dir/subdir"; - const subPath = path + "/subsubdir"; - Deno.mkdirSync(path, { recursive: true }); - Deno.mkdirSync(subPath); - pathInfo = Deno.statSync(path); - assert(pathInfo.isDirectory); // check exist first - const subPathInfo = Deno.statSync(subPath); - assert(subPathInfo.isDirectory); // check exist first - await Deno[method](path, { recursive: true }); // remove - // We then check parent directory again after remove - assertThrows(() => { - Deno.statSync(path); - }, Deno.errors.NotFound); - // Directory is gone - } - }, -); - -Deno.test( - { permissions: { write: true, read: true } }, - async function removeAllFileSuccess() { - for (const method of REMOVE_METHODS) { - // REMOVE FILE - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - Deno.writeFileSync(filename, data, { mode: 0o666 }); - const fileInfo = Deno.statSync(filename); - assert(fileInfo.isFile); // check exist first - await Deno[method](filename, { recursive: true }); // remove - // We then check again after remove - assertThrows(() => { - Deno.statSync(filename); - }, Deno.errors.NotFound); - // File is gone - } - }, -); - -Deno.test({ permissions: { write: true } }, async function removeAllFail() { - for (const method of REMOVE_METHODS) { - // NON-EXISTENT DIRECTORY/FILE - await assertRejects( - async () => { - // Non-existent - await Deno[method]("/baddir", { recursive: true }); - }, - Deno.errors.NotFound, - `remove '/baddir'`, - ); - } -}); - -Deno.test({ permissions: { write: false } }, async function removeAllPerm() { - for (const method of REMOVE_METHODS) { - await assertRejects(async () => { - await Deno[method]("/baddir", { recursive: true }); - }, Deno.errors.PermissionDenied); - } -}); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { write: true, read: true }, - }, - async function removeUnixSocketSuccess() { - for (const method of REMOVE_METHODS) { - // MAKE TEMPORARY UNIX SOCKET - const path = Deno.makeTempDirSync() + "/test.sock"; - const listener = Deno.listen({ transport: "unix", path }); - listener.close(); - Deno.statSync(path); // check if unix socket exists - - await Deno[method](path); - assertThrows(() => Deno.statSync(path), Deno.errors.NotFound); - } - }, -); - -if (Deno.build.os === "windows") { - Deno.test( - { permissions: { run: true, write: true, read: true } }, - async function removeFileSymlink() { - const { success } = await new Deno.Command("cmd", { - args: ["/c", "mklink", "file_link", "bar"], - stdout: "null", - }).output(); - - assert(success); - await Deno.remove("file_link"); - await assertRejects(async () => { - await Deno.lstat("file_link"); - }, Deno.errors.NotFound); - }, - ); - - Deno.test( - { permissions: { run: true, write: true, read: true } }, - async function removeDirSymlink() { - const { success } = await new Deno.Command("cmd", { - args: ["/c", "mklink", "/d", "dir_link", "bar"], - stdout: "null", - }).output(); - - assert(success); - await Deno.remove("dir_link"); - await assertRejects(async () => { - await Deno.lstat("dir_link"); - }, Deno.errors.NotFound); - }, - ); -} diff --git a/cli/tests/unit/rename_test.ts b/cli/tests/unit/rename_test.ts deleted file mode 100644 index 4f6bb09cf..000000000 --- a/cli/tests/unit/rename_test.ts +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - AssertionError, - assertIsError, - assertThrows, - pathToAbsoluteFileUrl, -} from "./test_util.ts"; - -function assertMissing(path: string) { - let caughtErr = false; - let info; - try { - info = Deno.lstatSync(path); - } catch (e) { - caughtErr = true; - assert(e instanceof Deno.errors.NotFound); - } - assert(caughtErr); - assertEquals(info, undefined); -} - -function assertFile(path: string) { - const info = Deno.lstatSync(path); - assert(info.isFile); -} - -function assertDirectory(path: string, mode?: number) { - const info = Deno.lstatSync(path); - assert(info.isDirectory); - if (Deno.build.os !== "windows" && mode !== undefined) { - assertEquals(info.mode! & 0o777, mode & ~Deno.umask()); - } -} - -Deno.test( - { permissions: { read: true, write: true } }, - function renameSyncSuccess() { - const testDir = Deno.makeTempDirSync(); - const oldpath = testDir + "/oldpath"; - const newpath = testDir + "/newpath"; - Deno.mkdirSync(oldpath); - Deno.renameSync(oldpath, newpath); - assertDirectory(newpath); - assertMissing(oldpath); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function renameSyncWithURL() { - const testDir = Deno.makeTempDirSync(); - const oldpath = testDir + "/oldpath"; - const newpath = testDir + "/newpath"; - Deno.mkdirSync(oldpath); - Deno.renameSync( - pathToAbsoluteFileUrl(oldpath), - pathToAbsoluteFileUrl(newpath), - ); - assertDirectory(newpath); - assertMissing(oldpath); - }, -); - -Deno.test( - { permissions: { read: false, write: true } }, - function renameSyncReadPerm() { - assertThrows(() => { - const oldpath = "/oldbaddir"; - const newpath = "/newbaddir"; - Deno.renameSync(oldpath, newpath); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, write: false } }, - function renameSyncWritePerm() { - assertThrows(() => { - const oldpath = "/oldbaddir"; - const newpath = "/newbaddir"; - Deno.renameSync(oldpath, newpath); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function renameSuccess() { - const testDir = Deno.makeTempDirSync(); - const oldpath = testDir + "/oldpath"; - const newpath = testDir + "/newpath"; - Deno.mkdirSync(oldpath); - await Deno.rename(oldpath, newpath); - assertDirectory(newpath); - assertMissing(oldpath); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function renameWithURL() { - const testDir = Deno.makeTempDirSync(); - const oldpath = testDir + "/oldpath"; - const newpath = testDir + "/newpath"; - Deno.mkdirSync(oldpath); - await Deno.rename( - pathToAbsoluteFileUrl(oldpath), - pathToAbsoluteFileUrl(newpath), - ); - assertDirectory(newpath); - assertMissing(oldpath); - }, -); - -function readFileString(filename: string): string { - const dataRead = Deno.readFileSync(filename); - const dec = new TextDecoder("utf-8"); - return dec.decode(dataRead); -} - -function writeFileString(filename: string, s: string) { - const enc = new TextEncoder(); - const data = enc.encode(s); - Deno.writeFileSync(filename, data, { mode: 0o666 }); -} - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - function renameSyncErrorsUnix() { - const testDir = Deno.makeTempDirSync(); - const oldfile = testDir + "/oldfile"; - const olddir = testDir + "/olddir"; - const emptydir = testDir + "/empty"; - const fulldir = testDir + "/dir"; - const file = fulldir + "/file"; - writeFileString(oldfile, "Hello"); - Deno.mkdirSync(olddir); - Deno.mkdirSync(emptydir); - Deno.mkdirSync(fulldir); - writeFileString(file, "world"); - - assertThrows( - () => { - Deno.renameSync(oldfile, emptydir); - }, - Error, - "Is a directory", - ); - try { - assertThrows( - () => { - Deno.renameSync(olddir, fulldir); - }, - Error, - "Directory not empty", - ); - } catch (e) { - // rename syscall may also return EEXIST, e.g. with XFS - assertIsError( - e, - AssertionError, - `Expected error message to include "Directory not empty", but got "File exists`, - ); - } - assertThrows( - () => { - Deno.renameSync(olddir, file); - }, - Error, - "Not a directory", - ); - assertThrows( - () => { - Deno.renameSync(olddir, file); - }, - Error, - `rename '${olddir}' -> '${file}'`, - ); - - const fileLink = testDir + "/fileLink"; - const dirLink = testDir + "/dirLink"; - const danglingLink = testDir + "/danglingLink"; - Deno.symlinkSync(file, fileLink); - Deno.symlinkSync(emptydir, dirLink); - Deno.symlinkSync(testDir + "/nonexistent", danglingLink); - - assertThrows( - () => { - Deno.renameSync(olddir, fileLink); - }, - Error, - "Not a directory", - ); - assertThrows( - () => { - Deno.renameSync(olddir, dirLink); - }, - Error, - "Not a directory", - ); - assertThrows( - () => { - Deno.renameSync(olddir, danglingLink); - }, - Error, - "Not a directory", - ); - - // should succeed on Unix - Deno.renameSync(olddir, emptydir); - Deno.renameSync(oldfile, dirLink); - Deno.renameSync(dirLink, danglingLink); - assertFile(danglingLink); - assertEquals("Hello", readFileString(danglingLink)); - }, -); - -Deno.test( - { - ignore: Deno.build.os !== "windows", - permissions: { read: true, write: true }, - }, - function renameSyncErrorsWin() { - const testDir = Deno.makeTempDirSync(); - const oldfile = testDir + "/oldfile"; - const olddir = testDir + "/olddir"; - const emptydir = testDir + "/empty"; - const fulldir = testDir + "/dir"; - const file = fulldir + "/file"; - writeFileString(oldfile, "Hello"); - Deno.mkdirSync(olddir); - Deno.mkdirSync(emptydir); - Deno.mkdirSync(fulldir); - writeFileString(file, "world"); - - assertThrows( - () => { - Deno.renameSync(oldfile, emptydir); - }, - Deno.errors.PermissionDenied, - "Access is denied", - ); - assertThrows( - () => { - Deno.renameSync(olddir, fulldir); - }, - Deno.errors.PermissionDenied, - "Access is denied", - ); - assertThrows( - () => { - Deno.renameSync(olddir, emptydir); - }, - Deno.errors.PermissionDenied, - "Access is denied", - ); - assertThrows( - () => { - Deno.renameSync(olddir, emptydir); - }, - Error, - `rename '${olddir}' -> '${emptydir}'`, - ); - - // should succeed on Windows - Deno.renameSync(olddir, file); - assertDirectory(file); - }, -); diff --git a/cli/tests/unit/request_test.ts b/cli/tests/unit/request_test.ts deleted file mode 100644 index fe34c20a5..000000000 --- a/cli/tests/unit/request_test.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals, assertStringIncludes } from "./test_util.ts"; - -Deno.test(async function fromInit() { - const req = new Request("http://foo/", { - body: "ahoyhoy", - method: "POST", - headers: { - "test-header": "value", - }, - }); - - assertEquals("ahoyhoy", await req.text()); - assertEquals(req.url, "http://foo/"); - assertEquals(req.headers.get("test-header"), "value"); -}); - -Deno.test(function requestNonString() { - const nonString = { - toString() { - return "http://foo/"; - }, - }; - // deno-lint-ignore ban-ts-comment - // @ts-expect-error - assertEquals(new Request(nonString).url, "http://foo/"); -}); - -Deno.test(function methodNonString() { - assertEquals(new Request("http://foo/", { method: undefined }).method, "GET"); -}); - -Deno.test(function requestRelativeUrl() { - assertEquals( - new Request("relative-url").url, - "http://127.0.0.1:4545/relative-url", - ); -}); - -Deno.test(async function cloneRequestBodyStream() { - // hack to get a stream - const stream = - new Request("http://foo/", { body: "a test body", method: "POST" }).body; - const r1 = new Request("http://foo/", { - body: stream, - method: "POST", - }); - - const r2 = r1.clone(); - - const b1 = await r1.text(); - const b2 = await r2.text(); - - assertEquals(b1, b2); -}); - -Deno.test(function customInspectFunction() { - const request = new Request("https://example.com"); - assertEquals( - Deno.inspect(request), - `Request { - bodyUsed: false, - headers: Headers {}, - method: "GET", - redirect: "follow", - url: "https://example.com/" -}`, - ); - assertStringIncludes(Deno.inspect(Request.prototype), "Request"); -}); - -Deno.test(function requestConstructorTakeURLObjectAsParameter() { - assertEquals( - new Request(new URL("http://foo/")).url, - "http://foo/", - ); -}); diff --git a/cli/tests/unit/resources_test.ts b/cli/tests/unit/resources_test.ts deleted file mode 100644 index 06558cdd1..000000000 --- a/cli/tests/unit/resources_test.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals, assertThrows } from "./test_util.ts"; - -const listenPort = 4505; - -Deno.test(function resourcesCloseBadArgs() { - assertThrows(() => { - Deno.close((null as unknown) as number); - }, TypeError); -}); - -Deno.test(function resourcesStdio() { - const res = Deno.resources(); - - assertEquals(res[0], "stdin"); - assertEquals(res[1], "stdout"); - assertEquals(res[2], "stderr"); -}); - -Deno.test({ permissions: { net: true } }, async function resourcesNet() { - const listener = Deno.listen({ port: listenPort }); - const dialerConn = await Deno.connect({ port: listenPort }); - const listenerConn = await listener.accept(); - - const res = Deno.resources(); - assertEquals( - Object.values(res).filter((r): boolean => r === "tcpListener").length, - 1, - ); - const tcpStreams = Object.values(res).filter( - (r): boolean => r === "tcpStream", - ); - assert(tcpStreams.length >= 2); - - listenerConn.close(); - dialerConn.close(); - listener.close(); -}); - -Deno.test({ permissions: { read: true } }, async function resourcesFile() { - const resourcesBefore = Deno.resources(); - const f = await Deno.open("cli/tests/testdata/assets/hello.txt"); - const resourcesAfter = Deno.resources(); - f.close(); - - // check that exactly one new resource (file) was added - assertEquals( - Object.keys(resourcesAfter).length, - Object.keys(resourcesBefore).length + 1, - ); - const newRid = +Object.keys(resourcesAfter).find((rid): boolean => { - return !Object.prototype.hasOwnProperty.call(resourcesBefore, rid); - })!; - assertEquals(resourcesAfter[newRid], "fsFile"); -}); diff --git a/cli/tests/unit/response_test.ts b/cli/tests/unit/response_test.ts deleted file mode 100644 index bbdd5f481..000000000 --- a/cli/tests/unit/response_test.ts +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertStringIncludes, - assertThrows, -} from "./test_util.ts"; - -Deno.test(async function responseText() { - const response = new Response("hello world"); - const textPromise = response.text(); - assert(textPromise instanceof Promise); - const text = await textPromise; - assert(typeof text === "string"); - assertEquals(text, "hello world"); -}); - -Deno.test(async function responseArrayBuffer() { - const response = new Response(new Uint8Array([1, 2, 3])); - const arrayBufferPromise = response.arrayBuffer(); - assert(arrayBufferPromise instanceof Promise); - const arrayBuffer = await arrayBufferPromise; - assert(arrayBuffer instanceof ArrayBuffer); - assertEquals(new Uint8Array(arrayBuffer), new Uint8Array([1, 2, 3])); -}); - -Deno.test(async function responseJson() { - const response = new Response('{"hello": "world"}'); - const jsonPromise = response.json(); - assert(jsonPromise instanceof Promise); - const json = await jsonPromise; - assert(json instanceof Object); - assertEquals(json, { hello: "world" }); -}); - -Deno.test(async function responseBlob() { - const response = new Response(new Uint8Array([1, 2, 3])); - const blobPromise = response.blob(); - assert(blobPromise instanceof Promise); - const blob = await blobPromise; - assert(blob instanceof Blob); - assertEquals(blob.size, 3); - assertEquals(await blob.arrayBuffer(), new Uint8Array([1, 2, 3]).buffer); -}); - -Deno.test(async function responseFormData() { - const input = new FormData(); - input.append("hello", "world"); - const response = new Response(input); - const contentType = response.headers.get("content-type")!; - assert(contentType.startsWith("multipart/form-data")); - const formDataPromise = response.formData(); - assert(formDataPromise instanceof Promise); - const formData = await formDataPromise; - assert(formData instanceof FormData); - assertEquals([...formData], [...input]); -}); - -Deno.test(function responseInvalidInit() { - // deno-lint-ignore ban-ts-comment - // @ts-expect-error - assertThrows(() => new Response("", 0)); - assertThrows(() => new Response("", { status: 0 })); - // deno-lint-ignore ban-ts-comment - // @ts-expect-error - assertThrows(() => new Response("", { status: null })); -}); - -Deno.test(function responseNullInit() { - // deno-lint-ignore ban-ts-comment - // @ts-expect-error - const response = new Response("", null); - assertEquals(response.status, 200); -}); - -Deno.test(function customInspectFunction() { - const response = new Response(); - assertEquals( - Deno.inspect(response), - `Response { - body: null, - bodyUsed: false, - headers: Headers {}, - ok: true, - redirected: false, - status: 200, - statusText: "", - url: "" -}`, - ); - assertStringIncludes(Deno.inspect(Response.prototype), "Response"); -}); - -Deno.test(async function responseBodyUsed() { - const response = new Response("body"); - assert(!response.bodyUsed); - await response.text(); - assert(response.bodyUsed); - // .body getter is needed so we can test the faulty code path - response.body; - assert(response.bodyUsed); -}); diff --git a/cli/tests/unit/serve_test.ts b/cli/tests/unit/serve_test.ts deleted file mode 100644 index b5c966d6f..000000000 --- a/cli/tests/unit/serve_test.ts +++ /dev/null @@ -1,3932 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assertMatch, assertRejects } from "@test_util/std/assert/mod.ts"; -import { Buffer, BufReader, BufWriter } from "@test_util/std/io/mod.ts"; -import { TextProtoReader } from "../testdata/run/textproto.ts"; -import { - assert, - assertEquals, - assertStringIncludes, - assertThrows, - execCode, - fail, - tmpUnixSocketPath, -} from "./test_util.ts"; - -// Since these tests may run in parallel, ensure this port is unique to this file -const servePort = 4502; - -const { - upgradeHttpRaw, - addTrailers, - serveHttpOnListener, - serveHttpOnConnection, - // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol -} = Deno[Deno.internal]; - -function createOnErrorCb(ac: AbortController): (err: unknown) => Response { - return (err) => { - console.error(err); - ac.abort(); - return new Response("Internal server error", { status: 500 }); - }; -} - -function onListen( - resolve: (value: void | PromiseLike<void>) => void, -): ({ hostname, port }: { hostname: string; port: number }) => void { - return () => { - resolve(); - }; -} - -async function makeServer( - handler: (req: Request) => Response | Promise<Response>, -): Promise< - { - finished: Promise<void>; - abort: () => void; - shutdown: () => Promise<void>; - [Symbol.asyncDispose](): PromiseLike<void>; - } -> { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler, - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - }); - - await promise; - return { - finished: server.finished, - abort() { - ac.abort(); - }, - async shutdown() { - await server.shutdown(); - }, - [Symbol.asyncDispose]() { - return server[Symbol.asyncDispose](); - }, - }; -} - -Deno.test(async function httpServerShutsDownPortBeforeResolving() { - const { finished, abort } = await makeServer((_req) => new Response("ok")); - assertThrows(() => Deno.listen({ port: servePort })); - abort(); - await finished; - - const listener = Deno.listen({ port: servePort }); - listener!.close(); -}); - -// When shutting down abruptly, we require that all in-progress connections are aborted, -// no new connections are allowed, and no new transactions are allowed on existing connections. -Deno.test( - { permissions: { net: true } }, - async function httpServerShutdownAbruptGuaranteeHttp11() { - const deferredQueue: { - input: ReturnType<typeof Promise.withResolvers<string>>; - out: ReturnType<typeof Promise.withResolvers<void>>; - }[] = []; - const { finished, abort } = await makeServer((_req) => { - const { input, out } = deferredQueue.shift()!; - return new Response( - new ReadableStream({ - async start(controller) { - controller.enqueue(new Uint8Array([46])); - out.resolve(); - controller.enqueue(encoder.encode(await input.promise)); - controller.close(); - }, - }), - ); - }); - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - const conn = await Deno.connect({ port: servePort }); - const w = conn.writable.getWriter(); - const r = conn.readable.getReader(); - - const deferred1 = { - input: Promise.withResolvers<string>(), - out: Promise.withResolvers<void>(), - }; - deferredQueue.push(deferred1); - const deferred2 = { - input: Promise.withResolvers<string>(), - out: Promise.withResolvers<void>(), - }; - deferredQueue.push(deferred2); - const deferred3 = { - input: Promise.withResolvers<string>(), - out: Promise.withResolvers<void>(), - }; - deferredQueue.push(deferred3); - deferred1.input.resolve("#"); - deferred2.input.resolve("$"); - await w.write(encoder.encode(`GET / HTTP/1.1\nConnection: keep-alive\n\n`)); - await w.write(encoder.encode(`GET / HTTP/1.1\nConnection: keep-alive\n\n`)); - - // Fully read two responses - let text = ""; - while (!text.includes("$\r\n")) { - text += decoder.decode((await r.read()).value); - } - - await w.write(encoder.encode(`GET / HTTP/1.1\nConnection: keep-alive\n\n`)); - await deferred3.out.promise; - - // This is half served, so wait for the chunk that has the first '.' - text = ""; - while (!text.includes("1\r\n.\r\n")) { - text += decoder.decode((await r.read()).value); - } - - abort(); - - // This doesn't actually write anything, but we release it after aborting - deferred3.input.resolve("!"); - - // Guarantee: can't connect to an aborted server (though this may not happen immediately) - let failed = false; - for (let i = 0; i < 10; i++) { - try { - const conn = await Deno.connect({ port: servePort }); - conn.close(); - // Give the runtime a few ticks to settle (required for Windows) - await new Promise((r) => setTimeout(r, 2 ** i)); - continue; - } catch (_) { - failed = true; - break; - } - } - assert(failed, "The Deno.serve listener was not disabled promptly"); - - // Guarantee: the pipeline is closed abruptly - assert((await r.read()).done); - - try { - conn.close(); - } catch (_) { - // Ignore - } - await finished; - }, -); - -// When shutting down abruptly, we require that all in-progress connections are aborted, -// no new connections are allowed, and no new transactions are allowed on existing connections. -Deno.test( - { permissions: { net: true } }, - async function httpServerShutdownGracefulGuaranteeHttp11() { - const deferredQueue: { - input: ReturnType<typeof Promise.withResolvers<string>>; - out: ReturnType<typeof Promise.withResolvers<void>>; - }[] = []; - const { finished, shutdown } = await makeServer((_req) => { - const { input, out } = deferredQueue.shift()!; - return new Response( - new ReadableStream({ - async start(controller) { - controller.enqueue(new Uint8Array([46])); - out.resolve(); - controller.enqueue(encoder.encode(await input.promise)); - controller.close(); - }, - }), - ); - }); - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - const conn = await Deno.connect({ port: servePort }); - const w = conn.writable.getWriter(); - const r = conn.readable.getReader(); - - const deferred1 = { - input: Promise.withResolvers<string>(), - out: Promise.withResolvers<void>(), - }; - deferredQueue.push(deferred1); - const deferred2 = { - input: Promise.withResolvers<string>(), - out: Promise.withResolvers<void>(), - }; - deferredQueue.push(deferred2); - const deferred3 = { - input: Promise.withResolvers<string>(), - out: Promise.withResolvers<void>(), - }; - deferredQueue.push(deferred3); - deferred1.input.resolve("#"); - deferred2.input.resolve("$"); - await w.write(encoder.encode(`GET / HTTP/1.1\nConnection: keep-alive\n\n`)); - await w.write(encoder.encode(`GET / HTTP/1.1\nConnection: keep-alive\n\n`)); - - // Fully read two responses - let text = ""; - while (!text.includes("$\r\n")) { - text += decoder.decode((await r.read()).value); - } - - await w.write(encoder.encode(`GET / HTTP/1.1\nConnection: keep-alive\n\n`)); - await deferred3.out.promise; - - // This is half served, so wait for the chunk that has the first '.' - text = ""; - while (!text.includes("1\r\n.\r\n")) { - text += decoder.decode((await r.read()).value); - } - - const shutdownPromise = shutdown(); - - // Release the final response _after_ we shut down - deferred3.input.resolve("!"); - - // Guarantee: can't connect to an aborted server (though this may not happen immediately) - let failed = false; - for (let i = 0; i < 10; i++) { - try { - const conn = await Deno.connect({ port: servePort }); - conn.close(); - // Give the runtime a few ticks to settle (required for Windows) - await new Promise((r) => setTimeout(r, 2 ** i)); - continue; - } catch (_) { - failed = true; - break; - } - } - assert(failed, "The Deno.serve listener was not disabled promptly"); - - // Guarantee: existing connections fully drain - while (!text.includes("!\r\n")) { - text += decoder.decode((await r.read()).value); - } - - await shutdownPromise; - - try { - conn.close(); - } catch (_) { - // Ignore - } - await finished; - }, -); - -// Ensure that resources don't leak during a graceful shutdown -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerShutdownGracefulResources() { - const { promise, resolve } = Promise.withResolvers<void>(); - const { finished, shutdown } = await makeServer(async (_req) => { - resolve(); - await new Promise((r) => setTimeout(r, 10)); - return new Response((await makeTempFile(1024 * 1024)).readable); - }); - - const f = fetch(`http://localhost:${servePort}`); - await promise; - assertEquals((await (await f).text()).length, 1048576); - await shutdown(); - await finished; - }, -); - -// Ensure that resources don't leak during a graceful shutdown -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerShutdownGracefulResources2() { - const waitForAbort = Promise.withResolvers<void>(); - const waitForRequest = Promise.withResolvers<void>(); - const { finished, shutdown } = await makeServer(async (_req) => { - waitForRequest.resolve(); - await waitForAbort.promise; - await new Promise((r) => setTimeout(r, 10)); - return new Response((await makeTempFile(1024 * 1024)).readable); - }); - - const f = fetch(`http://localhost:${servePort}`); - await waitForRequest.promise; - const s = shutdown(); - waitForAbort.resolve(); - assertEquals((await (await f).text()).length, 1048576); - await s; - await finished; - }, -); - -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerExplicitResourceManagement() { - let dataPromise; - - { - await using _server = await makeServer(async (_req) => { - return new Response((await makeTempFile(1024 * 1024)).readable); - }); - - const resp = await fetch(`http://localhost:${servePort}`); - dataPromise = resp.arrayBuffer(); - } - - assertEquals((await dataPromise).byteLength, 1048576); - }, -); - -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerExplicitResourceManagementManualClose() { - await using server = await makeServer(async (_req) => { - return new Response((await makeTempFile(1024 * 1024)).readable); - }); - - const resp = await fetch(`http://localhost:${servePort}`); - - const [_, data] = await Promise.all([ - server.shutdown(), - resp.arrayBuffer(), - ]); - - assertEquals(data.byteLength, 1048576); - }, -); - -Deno.test( - { permissions: { read: true, run: true } }, - async function httpServerUnref() { - const [statusCode, _output] = await execCode(` - async function main() { - const server = Deno.serve({ port: ${servePort}, handler: () => null }); - server.unref(); - await server.finished; // This doesn't block the program from exiting - } - main(); - `); - assertEquals(statusCode, 0); - }, -); - -Deno.test(async function httpServerCanResolveHostnames() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: (_req) => new Response("ok"), - hostname: "localhost", - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - - await promise; - const resp = await fetch(`http://localhost:${servePort}/`, { - headers: { "connection": "close" }, - }); - const text = await resp.text(); - assertEquals(text, "ok"); - ac.abort(); - await server.finished; -}); - -Deno.test(async function httpServerRejectsOnAddrInUse() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: (_req) => new Response("ok"), - hostname: "localhost", - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - await promise; - - assertThrows( - () => - Deno.serve({ - handler: (_req) => new Response("ok"), - hostname: "localhost", - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }), - Deno.errors.AddrInUse, - ); - ac.abort(); - await server.finished; -}); - -Deno.test({ permissions: { net: true } }, async function httpServerBasic() { - const ac = new AbortController(); - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: async (request, { remoteAddr }) => { - // FIXME(bartlomieju): - // make sure that request can be inspected - console.log(request); - assertEquals(new URL(request.url).href, `http://127.0.0.1:${servePort}/`); - assertEquals(await request.text(), ""); - assertEquals(remoteAddr.hostname, "127.0.0.1"); - deferred.resolve(); - return new Response("Hello World", { headers: { "foo": "bar" } }); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`, { - headers: { "connection": "close" }, - }); - await deferred.promise; - const clone = resp.clone(); - const text = await resp.text(); - assertEquals(text, "Hello World"); - assertEquals(resp.headers.get("foo"), "bar"); - const cloneText = await clone.text(); - assertEquals(cloneText, "Hello World"); - ac.abort(); - await server.finished; -}); - -// Test serving of HTTP on an arbitrary listener. -Deno.test( - { permissions: { net: true } }, - async function httpServerOnListener() { - const ac = new AbortController(); - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const listener = Deno.listen({ port: servePort }); - const server = serveHttpOnListener( - listener, - ac.signal, - async ( - request: Request, - { remoteAddr }: { remoteAddr: { hostname: string } }, - ) => { - assertEquals( - new URL(request.url).href, - `http://127.0.0.1:${servePort}/`, - ); - assertEquals(await request.text(), ""); - assertEquals(remoteAddr.hostname, "127.0.0.1"); - deferred.resolve(); - return new Response("Hello World", { headers: { "foo": "bar" } }); - }, - createOnErrorCb(ac), - onListen(listeningDeferred.resolve), - ); - - await listeningDeferred.promise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`, { - headers: { "connection": "close" }, - }); - await listeningDeferred.promise; - const clone = resp.clone(); - const text = await resp.text(); - assertEquals(text, "Hello World"); - assertEquals(resp.headers.get("foo"), "bar"); - const cloneText = await clone.text(); - assertEquals(cloneText, "Hello World"); - ac.abort(); - await server.finished; - }, -); - -// Test serving of HTTP on an arbitrary connection. -Deno.test( - { permissions: { net: true } }, - async function httpServerOnConnection() { - const ac = new AbortController(); - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const listener = Deno.listen({ port: servePort }); - const acceptPromise = listener.accept(); - const fetchPromise = fetch(`http://127.0.0.1:${servePort}/`, { - headers: { "connection": "close" }, - }); - - const server = serveHttpOnConnection( - await acceptPromise, - ac.signal, - async ( - request: Request, - { remoteAddr }: { remoteAddr: { hostname: string } }, - ) => { - assertEquals( - new URL(request.url).href, - `http://127.0.0.1:${servePort}/`, - ); - assertEquals(await request.text(), ""); - assertEquals(remoteAddr.hostname, "127.0.0.1"); - deferred.resolve(); - return new Response("Hello World", { headers: { "foo": "bar" } }); - }, - createOnErrorCb(ac), - onListen(listeningDeferred.resolve), - ); - - const resp = await fetchPromise; - await deferred.promise; - const clone = resp.clone(); - const text = await resp.text(); - assertEquals(text, "Hello World"); - assertEquals(resp.headers.get("foo"), "bar"); - const cloneText = await clone.text(); - assertEquals(cloneText, "Hello World"); - // Note that we don't need to abort this server -- it closes when the connection does - // ac.abort(); - await server.finished; - listener.close(); - }, -); - -Deno.test({ permissions: { net: true } }, async function httpServerOnError() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - let requestStash: Request | null; - - const server = Deno.serve({ - handler: async (request: Request) => { - requestStash = request; - await new Promise((r) => setTimeout(r, 100)); - throw "fail"; - }, - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: () => { - return new Response("failed: " + requestStash!.url, { status: 500 }); - }, - }); - - await promise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`, { - headers: { "connection": "close" }, - }); - const text = await resp.text(); - ac.abort(); - await server.finished; - - assertEquals(text, `failed: http://127.0.0.1:${servePort}/`); -}); - -Deno.test( - { permissions: { net: true } }, - async function httpServerOnErrorFails() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - // NOTE(bartlomieju): deno lint doesn't know that it's actually used later, - // but TypeScript can't see that either ¯\_(ツ)_/¯ - // deno-lint-ignore no-unused-vars - let requestStash: Request | null; - - const server = Deno.serve({ - handler: async (request: Request) => { - requestStash = request; - await new Promise((r) => setTimeout(r, 100)); - throw "fail"; - }, - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: () => { - throw "again"; - }, - }); - - await promise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`, { - headers: { "connection": "close" }, - }); - const text = await resp.text(); - ac.abort(); - await server.finished; - - assertEquals(text, "Internal Server Error"); - }, -); - -Deno.test({ permissions: { net: true } }, async function httpServerOverload1() { - const ac = new AbortController(); - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }, async (request) => { - // FIXME(bartlomieju): - // make sure that request can be inspected - console.log(request); - assertEquals(new URL(request.url).href, `http://127.0.0.1:${servePort}/`); - assertEquals(await request.text(), ""); - deferred.resolve(); - return new Response("Hello World", { headers: { "foo": "bar" } }); - }); - - await listeningDeferred.promise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`, { - headers: { "connection": "close" }, - }); - await deferred.promise; - const clone = resp.clone(); - const text = await resp.text(); - assertEquals(text, "Hello World"); - assertEquals(resp.headers.get("foo"), "bar"); - const cloneText = await clone.text(); - assertEquals(cloneText, "Hello World"); - ac.abort(); - await server.finished; -}); - -Deno.test({ permissions: { net: true } }, async function httpServerOverload2() { - const ac = new AbortController(); - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }, async (request) => { - // FIXME(bartlomieju): - // make sure that request can be inspected - console.log(request); - assertEquals(new URL(request.url).href, `http://127.0.0.1:${servePort}/`); - assertEquals(await request.text(), ""); - deferred.resolve(); - return new Response("Hello World", { headers: { "foo": "bar" } }); - }); - - await listeningDeferred.promise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`, { - headers: { "connection": "close" }, - }); - await deferred.promise; - const clone = resp.clone(); - const text = await resp.text(); - assertEquals(text, "Hello World"); - assertEquals(resp.headers.get("foo"), "bar"); - const cloneText = await clone.text(); - assertEquals(cloneText, "Hello World"); - ac.abort(); - await server.finished; -}); - -Deno.test( - { permissions: { net: true } }, - function httpServerErrorOverloadMissingHandler() { - // @ts-ignore - testing invalid overload - assertThrows(() => Deno.serve(), TypeError, "handler"); - // @ts-ignore - testing invalid overload - assertThrows(() => Deno.serve({}), TypeError, "handler"); - assertThrows( - // @ts-ignore - testing invalid overload - () => Deno.serve({ handler: undefined }), - TypeError, - "handler", - ); - assertThrows( - // @ts-ignore - testing invalid overload - () => Deno.serve(undefined, { handler: () => {} }), - TypeError, - "handler", - ); - }, -); - -Deno.test({ permissions: { net: true } }, async function httpServerPort0() { - const ac = new AbortController(); - - const server = Deno.serve({ - handler() { - return new Response("Hello World"); - }, - port: 0, - signal: ac.signal, - onListen({ port }) { - assert(port > 0 && port < 65536); - ac.abort(); - }, - }); - await server.finished; -}); - -Deno.test( - { permissions: { net: true } }, - async function httpServerDefaultOnListenCallback() { - const ac = new AbortController(); - - const consoleLog = console.log; - console.log = (msg) => { - try { - const match = msg.match(/Listening on http:\/\/localhost:(\d+)\//); - assert(!!match, `Didn't match ${msg}`); - const port = +match[1]; - assert(port > 0 && port < 65536); - } finally { - ac.abort(); - } - }; - - try { - const server = Deno.serve({ - handler() { - return new Response("Hello World"); - }, - hostname: "0.0.0.0", - port: 0, - signal: ac.signal, - }); - - await server.finished; - } finally { - console.log = consoleLog; - } - }, -); - -// https://github.com/denoland/deno/issues/15107 -Deno.test( - { permissions: { net: true } }, - async function httpLazyHeadersIssue15107() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - let headers: Headers; - const server = Deno.serve({ - handler: async (request) => { - await request.text(); - headers = request.headers; - deferred.resolve(); - return new Response(""); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - // Send GET request with a body + content-length. - const encoder = new TextEncoder(); - const body = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: 5\r\n\r\n12345`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await deferred.promise; - conn.close(); - assertEquals(headers!.get("content-length"), "5"); - ac.abort(); - await server.finished; - }, -); - -function createUrlTest( - name: string, - methodAndPath: string, - host: string | null, - expected: string, -) { - Deno.test(`httpServerUrl${name}`, async () => { - const listeningDeferred = Promise.withResolvers<number>(); - const urlDeferred = Promise.withResolvers<string>(); - const ac = new AbortController(); - const server = Deno.serve({ - handler: (request: Request) => { - urlDeferred.resolve(request.url); - return new Response(""); - }, - port: 0, - signal: ac.signal, - onListen: ({ port }: { port: number }) => { - listeningDeferred.resolve(port); - }, - onError: createOnErrorCb(ac), - }); - - const port = await listeningDeferred.promise; - const conn = await Deno.connect({ port }); - - const encoder = new TextEncoder(); - const body = `${methodAndPath} HTTP/1.1\r\n${ - host ? ("Host: " + host + "\r\n") : "" - }Content-Length: 5\r\n\r\n12345`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - try { - const expectedResult = expected.replace("HOST", "localhost").replace( - "PORT", - `${port}`, - ); - assertEquals(await urlDeferred.promise, expectedResult); - } finally { - ac.abort(); - await server.finished; - conn.close(); - } - }); -} - -createUrlTest("WithPath", "GET /path", null, "http://HOST:PORT/path"); -createUrlTest( - "WithPathAndHost", - "GET /path", - "deno.land", - "http://deno.land/path", -); -createUrlTest( - "WithAbsolutePath", - "GET http://localhost/path", - null, - "http://localhost/path", -); -createUrlTest( - "WithAbsolutePathAndHost", - "GET http://localhost/path", - "deno.land", - "http://localhost/path", -); -createUrlTest( - "WithPortAbsolutePath", - "GET http://localhost:1234/path", - null, - "http://localhost:1234/path", -); -createUrlTest( - "WithPortAbsolutePathAndHost", - "GET http://localhost:1234/path", - "deno.land", - "http://localhost:1234/path", -); -createUrlTest( - "WithPortAbsolutePathAndHostWithPort", - "GET http://localhost:1234/path", - "deno.land:9999", - "http://localhost:1234/path", -); - -createUrlTest("WithAsterisk", "OPTIONS *", null, "*"); -createUrlTest( - "WithAuthorityForm", - "CONNECT deno.land:80", - null, - "deno.land:80", -); - -// TODO(mmastrac): These should probably be 400 errors -createUrlTest("WithInvalidAsterisk", "GET *", null, "*"); -createUrlTest("WithInvalidNakedPath", "GET path", null, "path"); -createUrlTest( - "WithInvalidNakedAuthority", - "GET deno.land:1234", - null, - "deno.land:1234", -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerGetRequestBody() { - const deferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: (request) => { - assertEquals(request.body, null); - deferred.resolve(); - return new Response("", { headers: {} }); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - // Send GET request with a body + content-length. - const encoder = new TextEncoder(); - const body = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\nContent-Length: 5\r\n\r\n12345`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - const resp = new Uint8Array(200); - const readResult = await conn.read(resp); - assert(readResult); - assert(readResult > 0); - - conn.close(); - await deferred.promise; - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerAbortedRequestBody() { - const deferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: async (request) => { - await assertRejects(async () => { - await request.text(); - }); - deferred.resolve(); - // Not actually used - return new Response(); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - // Send POST request with a body + content-length, but don't send it all - const encoder = new TextEncoder(); - const body = - `POST / HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\nContent-Length: 10\r\n\r\n12345`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - conn.close(); - await deferred.promise; - ac.abort(); - await server.finished; - }, -); - -function createStreamTest(count: number, delay: number, action: string) { - function doAction(controller: ReadableStreamDefaultController, i: number) { - if (i == count) { - if (action == "Throw") { - controller.error(new Error("Expected error!")); - } else { - controller.close(); - } - } else { - controller.enqueue(`a${i}`); - - if (delay == 0) { - doAction(controller, i + 1); - } else { - setTimeout(() => doAction(controller, i + 1), delay); - } - } - } - - function makeStream(_count: number, delay: number): ReadableStream { - return new ReadableStream({ - start(controller) { - if (delay == 0) { - doAction(controller, 0); - } else { - setTimeout(() => doAction(controller, 0), delay); - } - }, - }).pipeThrough(new TextEncoderStream()); - } - - Deno.test(`httpServerStreamCount${count}Delay${delay}${action}`, async () => { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - const server = Deno.serve({ - handler: (_request) => { - return new Response(makeStream(count, delay)); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - - try { - await promise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`); - if (action == "Throw") { - await assertRejects(async () => { - await resp.text(); - }); - } else { - const text = await resp.text(); - - let expected = ""; - for (let i = 0; i < count; i++) { - expected += `a${i}`; - } - - assertEquals(text, expected); - } - } finally { - ac.abort(); - await server.shutdown(); - } - }); -} - -for (const count of [0, 1, 2, 3]) { - for (const delay of [0, 1, 25]) { - // Creating a stream that errors in start will throw - if (delay > 0) { - createStreamTest(count, delay, "Throw"); - } - createStreamTest(count, delay, "Close"); - } -} - -Deno.test( - { permissions: { net: true } }, - async function httpServerStreamRequest() { - const stream = new TransformStream(); - const writer = stream.writable.getWriter(); - writer.write(new TextEncoder().encode("hello ")); - writer.write(new TextEncoder().encode("world")); - writer.close(); - const { promise, resolve } = Promise.withResolvers<void>(); - const ac = new AbortController(); - const server = Deno.serve({ - handler: async (request) => { - const reqBody = await request.text(); - assertEquals("hello world", reqBody); - return new Response("yo"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - - await promise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`, { - body: stream.readable, - method: "POST", - headers: { "connection": "close" }, - }); - - assertEquals(await resp.text(), "yo"); - ac.abort(); - await server.finished; - }, -); - -Deno.test({ permissions: { net: true } }, async function httpServerClose() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - const server = Deno.serve({ - handler: () => new Response("ok"), - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - await promise; - const client = await Deno.connect({ port: servePort }); - client.close(); - ac.abort(); - await server.finished; -}); - -// https://github.com/denoland/deno/issues/15427 -Deno.test({ permissions: { net: true } }, async function httpServerCloseGet() { - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - const requestDeferred = Promise.withResolvers<void>(); - const responseDeferred = Promise.withResolvers<void>(); - const server = Deno.serve({ - handler: async () => { - requestDeferred.resolve(); - await new Promise((r) => setTimeout(r, 500)); - responseDeferred.resolve(); - return new Response("ok"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - const body = - `GET / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await requestDeferred.promise; - conn.close(); - await responseDeferred.promise; - ac.abort(); - await server.finished; -}); - -// FIXME: -Deno.test( - { permissions: { net: true } }, - async function httpServerEmptyBlobResponse() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - const server = Deno.serve({ - handler: () => new Response(new Blob([])), - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - - await promise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`); - const respBody = await resp.text(); - - assertEquals("", respBody); - ac.abort(); - await server.finished; - }, -); - -// https://github.com/denoland/deno/issues/17291 -Deno.test( - { permissions: { net: true } }, - async function httpServerIncorrectChunkedResponse() { - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - const errorDeferred = Promise.withResolvers<void>(); - const server = Deno.serve({ - handler: () => { - const body = new ReadableStream({ - start(controller) { - // Non-encoded string is not a valid readable chunk. - // @ts-ignore we're testing that input is invalid - controller.enqueue("wat"); - }, - type: "bytes", - }); - return new Response(body); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: (err) => { - const errResp = new Response( - `Internal server error: ${(err as Error).message}`, - { status: 500 }, - ); - errorDeferred.resolve(); - return errResp; - }, - }); - - await listeningDeferred.promise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`); - // Incorrectly implemented reader ReadableStream should reject. - assertStringIncludes(await resp.text(), "Failed to execute 'enqueue'"); - await errorDeferred.promise; - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerCorrectLengthForUnicodeString() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: () => new Response("韓國".repeat(10)), - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - - await promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - const body = - `GET / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - - conn.close(); - - ac.abort(); - await server.finished; - assert(msg.includes("content-length: 60")); - }, -); - -Deno.test({ permissions: { net: true } }, async function httpServerWebSocket() { - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - const doneDeferred = Promise.withResolvers<void>(); - const server = Deno.serve({ - handler: (request) => { - const { - response, - socket, - } = Deno.upgradeWebSocket(request); - socket.onerror = (e) => { - console.error(e); - fail(); - }; - socket.onmessage = (m) => { - socket.send(m.data); - socket.close(1001); - }; - socket.onclose = () => doneDeferred.resolve(); - return response; - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const def = Promise.withResolvers<void>(); - const ws = new WebSocket(`ws://localhost:${servePort}`); - ws.onmessage = (m) => assertEquals(m.data, "foo"); - ws.onerror = (e) => { - console.error(e); - fail(); - }; - ws.onclose = () => def.resolve(); - ws.onopen = () => ws.send("foo"); - - await def.promise; - await doneDeferred.promise; - ac.abort(); - await server.finished; -}); - -Deno.test( - { permissions: { net: true } }, - async function httpServerWebSocketRaw() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - const server = Deno.serve({ - handler: async (request) => { - const { conn, response } = upgradeHttpRaw(request); - const buf = new Uint8Array(1024); - let read; - - // Write our fake HTTP upgrade - await conn.write( - new TextEncoder().encode( - "HTTP/1.1 101 Switching Protocols\r\nConnection: Upgraded\r\n\r\nExtra", - ), - ); - - // Upgrade data - read = await conn.read(buf); - assertEquals( - new TextDecoder().decode(buf.subarray(0, read!)), - "Upgrade data", - ); - // Read the packet to echo - read = await conn.read(buf); - // Echo - await conn.write(buf.subarray(0, read!)); - - conn.close(); - return response; - }, - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - - await promise; - - const conn = await Deno.connect({ port: servePort }); - await conn.write( - new TextEncoder().encode( - "GET / HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\nUpgrade data", - ), - ); - const buf = new Uint8Array(1024); - let len; - - // Headers - let headers = ""; - for (let i = 0; i < 2; i++) { - len = await conn.read(buf); - headers += new TextDecoder().decode(buf.subarray(0, len!)); - if (headers.endsWith("Extra")) { - break; - } - } - assertMatch( - headers, - /HTTP\/1\.1 101 Switching Protocols[ ,.A-Za-z:0-9\r\n]*Extra/im, - ); - - // Data to echo - await conn.write(new TextEncoder().encode("buffer data")); - - // Echo - len = await conn.read(buf); - assertEquals( - new TextDecoder().decode(buf.subarray(0, len!)), - "buffer data", - ); - - conn.close(); - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerWebSocketUpgradeTwice() { - const ac = new AbortController(); - const done = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const server = Deno.serve({ - handler: (request) => { - const { - response, - socket, - } = Deno.upgradeWebSocket(request); - assertThrows( - () => { - Deno.upgradeWebSocket(request); - }, - Deno.errors.Http, - "already upgraded", - ); - socket.onerror = (e) => { - console.error(e); - fail(); - }; - socket.onmessage = (m) => { - socket.send(m.data); - socket.close(1001); - }; - socket.onclose = () => done.resolve(); - return response; - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const def = Promise.withResolvers<void>(); - const ws = new WebSocket(`ws://localhost:${servePort}`); - ws.onmessage = (m) => assertEquals(m.data, "foo"); - ws.onerror = (e) => { - console.error(e); - fail(); - }; - ws.onclose = () => def.resolve(); - ws.onopen = () => ws.send("foo"); - - await def.promise; - await done.promise; - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerWebSocketCloseFast() { - const ac = new AbortController(); - const done = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const server = Deno.serve({ - handler: (request) => { - const { - response, - socket, - } = Deno.upgradeWebSocket(request); - socket.onopen = () => socket.close(); - socket.onclose = () => done.resolve(); - return response; - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const def = Promise.withResolvers<void>(); - const ws = new WebSocket(`ws://localhost:${servePort}`); - ws.onerror = (e) => { - console.error(e); - fail(); - }; - ws.onclose = () => def.resolve(); - - await def.promise; - await done.promise; - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerWebSocketCanAccessRequest() { - const ac = new AbortController(); - const done = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const server = Deno.serve({ - handler: (request) => { - const { - response, - socket, - } = Deno.upgradeWebSocket(request); - socket.onerror = (e) => { - console.error(e); - fail(); - }; - socket.onmessage = (_m) => { - socket.send(request.url.toString()); - socket.close(1001); - }; - socket.onclose = () => done.resolve(); - return response; - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const def = Promise.withResolvers<void>(); - const ws = new WebSocket(`ws://localhost:${servePort}`); - ws.onmessage = (m) => - assertEquals(m.data, `http://localhost:${servePort}/`); - ws.onerror = (e) => { - console.error(e); - fail(); - }; - ws.onclose = () => def.resolve(); - ws.onopen = () => ws.send("foo"); - - await def.promise; - await done.promise; - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpVeryLargeRequest() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - let headers: Headers; - const server = Deno.serve({ - handler: (request) => { - headers = request.headers; - deferred.resolve(); - return new Response(""); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - // Send GET request with a body + content-length. - const encoder = new TextEncoder(); - const smthElse = "x".repeat(16 * 1024 + 256); - const body = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: 5\r\nSomething-Else: ${smthElse}\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await deferred.promise; - conn.close(); - assertEquals(headers!.get("content-length"), "5"); - assertEquals(headers!.get("something-else"), smthElse); - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpVeryLargeRequestAndBody() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - let headers: Headers; - let text: string; - const server = Deno.serve({ - handler: async (request) => { - headers = request.headers; - text = await request.text(); - deferred.resolve(); - return new Response(""); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - // Send GET request with a body + content-length. - const encoder = new TextEncoder(); - const smthElse = "x".repeat(16 * 1024 + 256); - const reqBody = "hello world".repeat(1024); - let body = - `PUT / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: ${reqBody.length}\r\nSomething-Else: ${smthElse}\r\n\r\n${reqBody}`; - - while (body.length > 0) { - const writeResult = await conn.write(encoder.encode(body)); - body = body.slice(writeResult); - } - - await deferred.promise; - conn.close(); - - assertEquals(headers!.get("content-length"), `${reqBody.length}`); - assertEquals(headers!.get("something-else"), smthElse); - assertEquals(text!, reqBody); - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpConnectionClose() { - const deferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: () => { - deferred.resolve(); - return new Response(""); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - // Send GET request with a body + connection: close. - const encoder = new TextEncoder(); - const body = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nConnection: Close\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - await deferred.promise; - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -async function testDuplex( - reader: ReadableStreamDefaultReader<Uint8Array>, - writable: WritableStreamDefaultWriter<Uint8Array>, -) { - await writable.write(new Uint8Array([1])); - const chunk1 = await reader.read(); - assert(!chunk1.done); - assertEquals(chunk1.value, new Uint8Array([1])); - await writable.write(new Uint8Array([2])); - const chunk2 = await reader.read(); - assert(!chunk2.done); - assertEquals(chunk2.value, new Uint8Array([2])); - await writable.close(); - const chunk3 = await reader.read(); - assert(chunk3.done); -} - -Deno.test( - { permissions: { net: true } }, - async function httpServerStreamDuplexDirect() { - const { promise, resolve } = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve( - { port: servePort, signal: ac.signal }, - (request: Request) => { - assert(request.body); - resolve(); - return new Response(request.body); - }, - ); - - const { readable, writable } = new TransformStream(); - const resp = await fetch(`http://127.0.0.1:${servePort}/`, { - method: "POST", - body: readable, - }); - - await promise; - assert(resp.body); - await testDuplex(resp.body.getReader(), writable.getWriter()); - ac.abort(); - await server.finished; - }, -); - -// Test that a duplex stream passing through JavaScript also works (ie: that the request body resource -// is still alive). https://github.com/denoland/deno/pull/20206 -Deno.test( - { permissions: { net: true } }, - async function httpServerStreamDuplexJavascript() { - const { promise, resolve } = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve( - { port: servePort, signal: ac.signal }, - (request: Request) => { - assert(request.body); - resolve(); - const reader = request.body.getReader(); - return new Response( - new ReadableStream({ - async pull(controller) { - await new Promise((r) => setTimeout(r, 100)); - const { done, value } = await reader.read(); - if (done) { - controller.close(); - } else { - controller.enqueue(value); - } - }, - }), - ); - }, - ); - - const { readable, writable } = new TransformStream(); - const resp = await fetch(`http://127.0.0.1:${servePort}/`, { - method: "POST", - body: readable, - }); - - await promise; - assert(resp.body); - await testDuplex(resp.body.getReader(), writable.getWriter()); - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - // Issue: https://github.com/denoland/deno/issues/10930 - async function httpServerStreamingResponse() { - // This test enqueues a single chunk for readable - // stream and waits for client to read that chunk and signal - // it before enqueueing subsequent chunk. Issue linked above - // presented a situation where enqueued chunks were not - // written to the HTTP connection until the next chunk was enqueued. - const listeningDeferred = Promise.withResolvers<void>(); - const deferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - let counter = 0; - - const deferreds = [ - Promise.withResolvers<void>(), - Promise.withResolvers<void>(), - Promise.withResolvers<void>(), - ]; - - async function writeRequest(conn: Deno.Conn) { - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - const w = new BufWriter(conn); - const r = new BufReader(conn); - const body = `GET / HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\n\r\n`; - const writeResult = await w.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await w.flush(); - const tpr = new TextProtoReader(r); - const statusLine = await tpr.readLine(); - assert(statusLine !== null); - const headers = await tpr.readMimeHeader(); - assert(headers !== null); - - const chunkedReader = chunkedBodyReader(headers, r); - - const buf = new Uint8Array(5); - const dest = new Buffer(); - - let result: number | null; - - try { - while ((result = await chunkedReader.read(buf)) !== null) { - const len = Math.min(buf.byteLength, result); - - await dest.write(buf.subarray(0, len)); - - // Resolve a deferred - this will make response stream to - // enqueue next chunk. - deferreds[counter - 1].resolve(); - } - return decoder.decode(dest.bytes()); - } catch (e) { - console.error(e); - } - } - - function periodicStream() { - return new ReadableStream({ - start(controller) { - controller.enqueue(`${counter}\n`); - counter++; - }, - - async pull(controller) { - if (counter >= 3) { - return controller.close(); - } - - await deferreds[counter - 1].promise; - - controller.enqueue(`${counter}\n`); - counter++; - }, - }).pipeThrough(new TextEncoderStream()); - } - - const server = Deno.serve({ - handler: () => { - deferred.resolve(); - return new Response(periodicStream()); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - // start a client - const clientConn = await Deno.connect({ port: servePort }); - - const r1 = await writeRequest(clientConn); - assertEquals(r1, "0\n1\n2\n"); - - ac.abort(); - await deferred.promise; - await server.finished; - clientConn.close(); - }, -); - -// Make sure that the chunks of a large response aren't repeated or corrupted in some other way by -// scatterning sentinels throughout. -// https://github.com/denoland/fresh/issues/1699 -Deno.test( - { permissions: { net: true } }, - async function httpLargeReadableStreamChunk() { - const ac = new AbortController(); - const server = Deno.serve({ - handler() { - return new Response( - new ReadableStream({ - start(controller) { - const buffer = new Uint8Array(1024 * 1024); - // Mark the buffer with sentinels - for (let i = 0; i < 256; i++) { - buffer[i * 4096] = i; - } - controller.enqueue(buffer); - controller.close(); - }, - }), - ); - }, - port: servePort, - signal: ac.signal, - }); - const response = await fetch(`http://localhost:${servePort}/`); - const body = await response.arrayBuffer(); - assertEquals(1024 * 1024, body.byteLength); - const buffer = new Uint8Array(body); - for (let i = 0; i < 256; i++) { - assertEquals( - i, - buffer[i * 4096], - `sentinel mismatch at index ${i * 4096}`, - ); - } - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpRequestLatin1Headers() { - const listeningDeferred = Promise.withResolvers<void>(); - const deferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - const server = Deno.serve({ - handler: (request) => { - assertEquals(request.headers.get("X-Header-Test"), "á"); - deferred.resolve(); - return new Response("hello", { headers: { "X-Header-Test": "Æ" } }); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const clientConn = await Deno.connect({ port: servePort }); - const requestText = - `GET / HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\nX-Header-Test: á\r\n\r\n`; - const requestBytes = new Uint8Array(requestText.length); - for (let i = 0; i < requestText.length; i++) { - requestBytes[i] = requestText.charCodeAt(i); - } - let written = 0; - while (written < requestBytes.byteLength) { - written += await clientConn.write(requestBytes.slice(written)); - } - - const buf = new Uint8Array(1024); - await clientConn.read(buf); - - await deferred.promise; - const responseText = new TextDecoder("iso-8859-1").decode(buf); - clientConn.close(); - - ac.abort(); - await server.finished; - - assertMatch(responseText, /\r\n[Xx]-[Hh]eader-[Tt]est: Æ\r\n/); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerRequestWithoutPath() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - // FIXME: - // assertEquals(new URL(request.url).href, `http://127.0.0.1:${servePort}/`); - assertEquals(await request.text(), ""); - deferred.resolve(); - return new Response("11"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const clientConn = await Deno.connect({ port: servePort }); - - async function writeRequest(conn: Deno.Conn) { - const encoder = new TextEncoder(); - - const w = new BufWriter(conn); - const r = new BufReader(conn); - const body = - `CONNECT 127.0.0.1:${servePort} HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\n\r\n`; - const writeResult = await w.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await w.flush(); - const tpr = new TextProtoReader(r); - const statusLine = await tpr.readLine(); - assert(statusLine !== null); - const m = statusLine.match(/^(.+?) (.+?) (.+?)$/); - assert(m !== null, "must be matched"); - const [_, _proto, status, _ok] = m; - assertEquals(status, "200"); - const headers = await tpr.readMimeHeader(); - assert(headers !== null); - } - - await writeRequest(clientConn); - clientConn.close(); - await deferred.promise; - - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpCookieConcatenation() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(await request.text(), ""); - assertEquals(request.headers.get("cookie"), "foo=bar; bar=foo"); - deferred.resolve(); - return new Response("ok"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - reusePort: true, - }); - - await listeningDeferred.promise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`, { - headers: [ - ["connection", "close"], - ["cookie", "foo=bar"], - ["cookie", "bar=foo"], - ], - }); - await deferred.promise; - - const text = await resp.text(); - assertEquals(text, "ok"); - - ac.abort(); - await server.finished; - }, -); - -// https://github.com/denoland/deno/issues/12741 -// https://github.com/denoland/deno/pull/12746 -// https://github.com/denoland/deno/pull/12798 -Deno.test( - { permissions: { net: true, run: true } }, - async function httpServerDeleteRequestHasBody() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const hostname = "localhost"; - - const server = Deno.serve({ - handler: () => { - deferred.resolve(); - return new Response("ok"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const url = `http://${hostname}:${servePort}/`; - const args = ["-X", "DELETE", url]; - const { success } = await new Deno.Command("curl", { - args, - stdout: "null", - stderr: "null", - }).output(); - assert(success); - await deferred.promise; - ac.abort(); - - await server.finished; - }, -); - -// FIXME: -Deno.test( - { permissions: { net: true } }, - async function httpServerRespondNonAsciiUint8Array() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: (request) => { - assertEquals(request.body, null); - deferred.resolve(); - return new Response(new Uint8Array([128])); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - await listeningDeferred.resolve; - const resp = await fetch(`http://localhost:${servePort}/`); - - await deferred.promise; - - assertEquals(resp.status, 200); - const body = await resp.arrayBuffer(); - assertEquals(new Uint8Array(body), new Uint8Array([128])); - - ac.abort(); - await server.finished; - }, -); - -// Some of these tests are ported from Hyper -// https://github.com/hyperium/hyper/blob/889fa2d87252108eb7668b8bf034ffcc30985117/src/proto/h1/role.rs -// https://github.com/hyperium/hyper/blob/889fa2d87252108eb7668b8bf034ffcc30985117/tests/server.rs - -Deno.test( - { permissions: { net: true } }, - async function httpServerParseRequest() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: (request) => { - assertEquals(request.method, "GET"); - assertEquals(request.headers.get("host"), "deno.land"); - deferred.resolve(); - return new Response("ok"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - const body = `GET /echo HTTP/1.1\r\nHost: deno.land\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await deferred.promise; - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerParseHeaderHtabs() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: (request) => { - assertEquals(request.method, "GET"); - assertEquals(request.headers.get("server"), "hello\tworld"); - deferred.resolve(); - return new Response("ok"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - const body = `GET / HTTP/1.1\r\nserver: hello\tworld\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await deferred.promise; - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerGetShouldIgnoreBody() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(request.method, "GET"); - assertEquals(await request.text(), ""); - deferred.resolve(); - return new Response("ok"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - // Connection: close = don't try to parse the body as a new request - const body = - `GET / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\nI shouldn't be read.\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await deferred.promise; - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerPostWithBody() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(request.method, "POST"); - assertEquals(await request.text(), "I'm a good request."); - deferred.resolve(); - return new Response("ok"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - const body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: 19\r\n\r\nI'm a good request.`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await deferred.promise; - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -type TestCase = { - headers?: Record<string, string>; - // deno-lint-ignore no-explicit-any - body: any; - expectsChunked?: boolean; - expectsConnLen?: boolean; -}; - -function hasHeader(msg: string, name: string): boolean { - const n = msg.indexOf("\r\n\r\n") || msg.length; - return msg.slice(0, n).includes(name); -} - -function createServerLengthTest(name: string, testCase: TestCase) { - Deno.test(name, async function () { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: (request) => { - assertEquals(request.method, "GET"); - deferred.resolve(); - return new Response(testCase.body, testCase.headers ?? {}); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - const body = - `GET / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await deferred.promise; - - const decoder = new TextDecoder(); - let msg = ""; - while (true) { - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - if (!readResult) { - break; - } - msg += decoder.decode(buf.subarray(0, readResult)); - try { - assert( - testCase.expectsChunked == hasHeader(msg, "Transfer-Encoding:"), - ); - assert(testCase.expectsChunked == hasHeader(msg, "chunked")); - assert(testCase.expectsConnLen == hasHeader(msg, "Content-Length:")); - - const n = msg.indexOf("\r\n\r\n") + 4; - - if (testCase.expectsChunked) { - assertEquals(msg.slice(n + 1, n + 3), "\r\n"); - assertEquals(msg.slice(msg.length - 7), "\r\n0\r\n\r\n"); - } - - if (testCase.expectsConnLen && typeof testCase.body === "string") { - assertEquals(msg.slice(n), testCase.body); - } - break; - } catch { - continue; - } - } - - conn.close(); - - ac.abort(); - await server.finished; - }); -} - -// Quick and dirty way to make a readable stream from a string. Alternatively, -// `readableStreamFromReader(file)` could be used. -function stream(s: string): ReadableStream<Uint8Array> { - return new Response(s).body!; -} - -createServerLengthTest("fixedResponseKnown", { - headers: { "content-length": "11" }, - body: "foo bar baz", - expectsChunked: false, - expectsConnLen: true, -}); - -createServerLengthTest("fixedResponseUnknown", { - headers: { "content-length": "11" }, - body: stream("foo bar baz"), - expectsChunked: true, - expectsConnLen: false, -}); - -createServerLengthTest("fixedResponseKnownEmpty", { - headers: { "content-length": "0" }, - body: "", - expectsChunked: false, - expectsConnLen: true, -}); - -createServerLengthTest("chunkedRespondKnown", { - headers: { "transfer-encoding": "chunked" }, - body: "foo bar baz", - expectsChunked: false, - expectsConnLen: true, -}); - -createServerLengthTest("chunkedRespondUnknown", { - headers: { "transfer-encoding": "chunked" }, - body: stream("foo bar baz"), - expectsChunked: true, - expectsConnLen: false, -}); - -createServerLengthTest("autoResponseWithKnownLength", { - body: "foo bar baz", - expectsChunked: false, - expectsConnLen: true, -}); - -createServerLengthTest("autoResponseWithUnknownLength", { - body: stream("foo bar baz"), - expectsChunked: true, - expectsConnLen: false, -}); - -createServerLengthTest("autoResponseWithKnownLengthEmpty", { - body: "", - expectsChunked: false, - expectsConnLen: true, -}); - -createServerLengthTest("autoResponseWithUnknownLengthEmpty", { - body: stream(""), - expectsChunked: true, - expectsConnLen: false, -}); - -Deno.test( - { permissions: { net: true } }, - async function httpServerPostWithContentLengthBody() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(request.method, "POST"); - assertEquals(request.headers.get("content-length"), "5"); - assertEquals(await request.text(), "hello"); - deferred.resolve(); - return new Response("ok"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - - const body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: 5\r\n\r\nhello`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await deferred.promise; - - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerPostWithInvalidPrefixContentLength() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - const server = Deno.serve({ - handler: () => { - throw new Error("unreachable"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - - await promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - const body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: +5\r\n\r\nhello`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - assert(msg.includes("HTTP/1.1 400 Bad Request")); - - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerPostWithChunkedBody() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals(request.method, "POST"); - assertEquals(await request.text(), "qwert"); - deferred.resolve(); - return new Response("ok"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - - const body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nq\r\n2\r\nwe\r\n2\r\nrt\r\n0\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await deferred.promise; - - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerPostWithIncompleteBody() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (r) => { - deferred.resolve(); - assertEquals(await r.text(), "12345"); - return new Response("ok"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - - const body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: 10\r\n\r\n12345`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - await deferred.promise; - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerHeadResponseDoesntSendBody() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: () => { - deferred.resolve(); - return new Response("NaN".repeat(100)); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - const body = - `HEAD / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - await deferred.promise; - - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - - assert(msg.includes("content-length: 300\r\n")); - - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -function makeTempData(size: number) { - return new Uint8Array(size).fill(1); -} - -async function makeTempFile(size: number) { - const tmpFile = await Deno.makeTempFile(); - using file = await Deno.open(tmpFile, { write: true, read: true }); - const data = makeTempData(size); - await file.write(data); - - return await Deno.open(tmpFile, { write: true, read: true }); -} - -const compressionTestCases = [ - { name: "Empty", length: 0, in: {}, out: {}, expect: null }, - { - name: "EmptyAcceptGzip", - length: 0, - in: { "Accept-Encoding": "gzip" }, - out: {}, - expect: null, - }, - // This technically would be compressible if not for the size, however the size_hint is not implemented - // for FileResource and we don't currently peek ahead on resources. - // { - // name: "EmptyAcceptGzip2", - // length: 0, - // in: { "Accept-Encoding": "gzip" }, - // out: { "Content-Type": "text/plain" }, - // expect: null, - // }, - { name: "Incompressible", length: 1024, in: {}, out: {}, expect: null }, - { - name: "IncompressibleAcceptGzip", - length: 1024, - in: { "Accept-Encoding": "gzip" }, - out: {}, - expect: null, - }, - { - name: "IncompressibleType", - length: 1024, - in: { "Accept-Encoding": "gzip" }, - out: { "Content-Type": "text/fake" }, - expect: null, - }, - { - name: "CompressibleType", - length: 1024, - in: { "Accept-Encoding": "gzip" }, - out: { "Content-Type": "text/plain" }, - expect: "gzip", - }, - { - name: "CompressibleType2", - length: 1024, - in: { "Accept-Encoding": "gzip, deflate, br" }, - out: { "Content-Type": "text/plain" }, - expect: "gzip", - }, - { - name: "CompressibleType3", - length: 1024, - in: { "Accept-Encoding": "br" }, - out: { "Content-Type": "text/plain" }, - expect: "br", - }, - { - name: "IncompressibleRange", - length: 1024, - in: { "Accept-Encoding": "gzip" }, - out: { "Content-Type": "text/plain", "Content-Range": "1" }, - expect: null, - }, - { - name: "IncompressibleCE", - length: 1024, - in: { "Accept-Encoding": "gzip" }, - out: { "Content-Type": "text/plain", "Content-Encoding": "random" }, - expect: null, - }, - { - name: "IncompressibleCC", - length: 1024, - in: { "Accept-Encoding": "gzip" }, - out: { "Content-Type": "text/plain", "Cache-Control": "no-transform" }, - expect: null, - }, - { - name: "BadHeader", - length: 1024, - in: { "Accept-Encoding": "\x81" }, - out: { "Content-Type": "text/plain", "Cache-Control": "no-transform" }, - expect: null, - }, -]; - -for (const testCase of compressionTestCases) { - const name = `httpServerCompression${testCase.name}`; - Deno.test( - { permissions: { net: true, write: true, read: true } }, - { - [name]: async function () { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - const server = Deno.serve({ - handler: async (_request) => { - const f = await makeTempFile(testCase.length); - deferred.resolve(); - // deno-lint-ignore no-explicit-any - const headers = testCase.out as any; - headers["Content-Length"] = testCase.length.toString(); - return new Response(f.readable, { - headers: headers as HeadersInit, - }); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - try { - await listeningDeferred.promise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`, { - headers: testCase.in as HeadersInit, - }); - await deferred.promise; - const body = await resp.arrayBuffer(); - if (testCase.expect == null) { - assertEquals(body.byteLength, testCase.length); - assertEquals( - resp.headers.get("content-length"), - testCase.length.toString(), - ); - assertEquals( - resp.headers.get("content-encoding"), - testCase.out["Content-Encoding"] || null, - ); - } else if (testCase.expect == "gzip") { - // Note the fetch will transparently decompress this response, BUT we can detect that a response - // was compressed by the lack of a content length. - assertEquals(body.byteLength, testCase.length); - assertEquals(resp.headers.get("content-encoding"), null); - assertEquals(resp.headers.get("content-length"), null); - } - } finally { - ac.abort(); - await server.finished; - } - }, - }[name], - ); -} - -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerPostFile() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (request) => { - assertEquals( - new Uint8Array(await request.arrayBuffer()), - makeTempData(70 * 1024), - ); - deferred.resolve(); - return new Response("ok"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const f = await makeTempFile(70 * 1024); - const response = await fetch(`http://localhost:${servePort}/`, { - method: "POST", - body: f.readable, - }); - - await deferred.promise; - - assertEquals(response.status, 200); - assertEquals(await response.text(), "ok"); - - ac.abort(); - await server.finished; - }, -); - -for (const delay of ["delay", "nodelay"]) { - for (const url of ["text", "file", "stream"]) { - // Ensure that we don't panic when the incoming TCP request was dropped - // https://github.com/denoland/deno/issues/20315 and that we correctly - // close/cancel the response - Deno.test({ - permissions: { read: true, write: true, net: true }, - name: `httpServerTcpCancellation_${url}_${delay}`, - fn: async function () { - const ac = new AbortController(); - const streamCancelled = url == "stream" - ? Promise.withResolvers<void>() - : undefined; - const listeningDeferred = Promise.withResolvers<void>(); - const waitForAbort = Promise.withResolvers<void>(); - const waitForRequest = Promise.withResolvers<void>(); - const server = Deno.serve({ - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - handler: async (req: Request) => { - let respBody = null; - if (req.url.includes("/text")) { - respBody = "text"; - } else if (req.url.includes("/file")) { - respBody = (await makeTempFile(1024)).readable; - } else if (req.url.includes("/stream")) { - respBody = new ReadableStream({ - start(controller) { - controller.enqueue(new Uint8Array([1])); - }, - cancel(reason) { - streamCancelled!.resolve(reason); - }, - }); - } else { - fail(); - } - waitForRequest.resolve(); - await waitForAbort.promise; - - if (delay == "delay") { - await new Promise((r) => setTimeout(r, 1000)); - } - // Allocate the request body - req.body; - return new Response(respBody); - }, - }); - - await listeningDeferred.promise; - - // Create a POST request and drop it once the server has received it - const conn = await Deno.connect({ port: servePort }); - const writer = conn.writable.getWriter(); - await writer.write( - new TextEncoder().encode(`POST /${url} HTTP/1.0\n\n`), - ); - await waitForRequest.promise; - await writer.close(); - - waitForAbort.resolve(); - - // Wait for cancellation before we shut the server down - if (streamCancelled !== undefined) { - await streamCancelled; - } - - // Since the handler has a chance of creating resources or running async - // ops, we need to use a graceful shutdown here to ensure they have fully - // drained. - await server.shutdown(); - - await server.finished; - }, - }); - } -} - -Deno.test( - { permissions: { net: true } }, - async function httpServerCancelFetch() { - const request2 = Promise.withResolvers<void>(); - const request2Aborted = Promise.withResolvers<string>(); - const { finished, abort } = await makeServer(async (req) => { - if (req.url.endsWith("/1")) { - const fetchRecursive = await fetch(`http://localhost:${servePort}/2`); - return new Response(fetchRecursive.body); - } else if (req.url.endsWith("/2")) { - request2.resolve(); - return new Response( - new ReadableStream({ - start(_controller) {/* just hang */}, - cancel(reason) { - request2Aborted.resolve(reason); - }, - }), - ); - } - fail(); - }); - const fetchAbort = new AbortController(); - const fetchPromise = await fetch(`http://localhost:${servePort}/1`, { - signal: fetchAbort.signal, - }); - await fetchPromise; - await request2.promise; - fetchAbort.abort(); - assertEquals("resource closed", await request2Aborted.promise); - - abort(); - await finished; - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function httpServerWithTls() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - const hostname = "127.0.0.1"; - - const server = Deno.serve({ - handler: () => new Response("Hello World"), - hostname, - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - cert: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.crt"), - key: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.key"), - }); - - await promise; - const caCert = Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem"); - const client = Deno.createHttpClient({ caCerts: [caCert] }); - const resp = await fetch(`https://localhost:${servePort}/`, { - client, - headers: { "connection": "close" }, - }); - - const respBody = await resp.text(); - assertEquals("Hello World", respBody); - - client.close(); - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerRequestCLTE() { - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - const deferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: async (req) => { - assertEquals(await req.text(), ""); - deferred.resolve(); - return new Response("ok"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - - const body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: 13\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\nEXTRA`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await deferred.promise; - - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true, write: true, read: true } }, - async function httpServerRequestTETE() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: () => { - throw new Error("oops"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - const variations = [ - "Transfer-Encoding : chunked", - "Transfer-Encoding: xchunked", - "Transfer-Encoding: chunkedx", - "Transfer-Encoding\n: chunked", - ]; - - await promise; - for (const teHeader of variations) { - const conn = await Deno.connect({ port: servePort }); - const body = - `POST / HTTP/1.1\r\nHost: example.domain\r\n${teHeader}\r\n\r\n0\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - assert(msg.includes("HTTP/1.1 400 Bad Request\r\n")); - - conn.close(); - } - - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServer204ResponseDoesntSendContentLength() { - const { promise, resolve } = Promise.withResolvers<void>(); - const ac = new AbortController(); - const server = Deno.serve({ - handler: (_request) => new Response(null, { status: 204 }), - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - - try { - await promise; - const resp = await fetch(`http://127.0.0.1:${servePort}/`, { - method: "GET", - headers: { "connection": "close" }, - }); - assertEquals(resp.status, 204); - assertEquals(resp.headers.get("Content-Length"), null); - } finally { - ac.abort(); - await server.finished; - } - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServer304ResponseDoesntSendBody() { - const deferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: () => { - deferred.resolve(); - return new Response(null, { status: 304 }); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - const body = - `GET / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - - await deferred.promise; - - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - - assert(msg.startsWith("HTTP/1.1 304 Not Modified")); - assert(msg.endsWith("\r\n\r\n")); - - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerExpectContinue() { - const deferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: async (req) => { - deferred.resolve(); - assertEquals(await req.text(), "hello"); - return new Response(null, { status: 304 }); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - { - const body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nExpect: 100-continue\r\nContent-Length: 5\r\nConnection: close\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - } - - await deferred.promise; - - { - const msgExpected = "HTTP/1.1 100 Continue\r\n\r\n"; - const buf = new Uint8Array(encoder.encode(msgExpected).byteLength); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - assert(msg.startsWith(msgExpected)); - } - - { - const body = "hello"; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - } - - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - - assert(msg.startsWith("HTTP/1.1 304 Not Modified")); - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function httpServerExpectContinueButNoBodyLOL() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve({ - handler: async (req) => { - deferred.resolve(); - assertEquals(await req.text(), ""); - return new Response(null, { status: 304 }); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), - onError: createOnErrorCb(ac), - }); - - await listeningDeferred.promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - { - // // no content-length or transfer-encoding means no body! - const body = - `POST / HTTP/1.1\r\nHost: example.domain\r\nExpect: 100-continue\r\nConnection: close\r\n\r\n`; - const writeResult = await conn.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - } - - await deferred.promise; - - const buf = new Uint8Array(1024); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - - assert(msg.startsWith("HTTP/1.1 304 Not Modified")); - conn.close(); - - ac.abort(); - await server.finished; - }, -); - -const badRequests = [ - ["weirdMethodName", "GE T / HTTP/1.1\r\n\r\n"], - ["illegalRequestLength", "POST / HTTP/1.1\r\nContent-Length: foo\r\n\r\n"], - ["illegalRequestLength2", "POST / HTTP/1.1\r\nContent-Length: -1\r\n\r\n"], - ["illegalRequestLength3", "POST / HTTP/1.1\r\nContent-Length: 1.1\r\n\r\n"], - ["illegalRequestLength4", "POST / HTTP/1.1\r\nContent-Length: 1.\r\n\r\n"], -]; - -for (const [name, req] of badRequests) { - const testFn = { - [name]: async () => { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: () => { - throw new Error("oops"); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - - await promise; - const conn = await Deno.connect({ port: servePort }); - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - { - const writeResult = await conn.write(encoder.encode(req)); - assertEquals(req.length, writeResult); - } - - const buf = new Uint8Array(100); - const readResult = await conn.read(buf); - assert(readResult); - const msg = decoder.decode(buf.subarray(0, readResult)); - - assert(msg.startsWith("HTTP/1.1 400 ")); - conn.close(); - - ac.abort(); - await server.finished; - }, - }[name]; - - Deno.test( - { permissions: { net: true } }, - testFn, - ); -} - -Deno.test( - { permissions: { net: true } }, - async function httpServerConcurrentRequests() { - const ac = new AbortController(); - const { resolve } = Promise.withResolvers<void>(); - - let reqCount = -1; - let timerId: number | undefined; - const server = Deno.serve({ - handler: (_req) => { - reqCount++; - if (reqCount === 0) { - const msg = new TextEncoder().encode("data: hello\r\n\r\n"); - // SSE - const body = new ReadableStream({ - start(controller) { - timerId = setInterval(() => { - controller.enqueue(msg); - }, 1000); - }, - cancel() { - if (typeof timerId === "number") { - clearInterval(timerId); - } - }, - }); - return new Response(body, { - headers: { - "Content-Type": "text/event-stream", - }, - }); - } - - return new Response(`hello ${reqCount}`); - }, - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - - const sseRequest = await fetch(`http://localhost:${servePort}/`); - - const decoder = new TextDecoder(); - const stream = sseRequest.body!.getReader(); - { - const { done, value } = await stream.read(); - assert(!done); - assertEquals(decoder.decode(value), "data: hello\r\n\r\n"); - } - - const helloRequest = await fetch(`http://localhost:${servePort}/`); - assertEquals(helloRequest.status, 200); - assertEquals(await helloRequest.text(), "hello 1"); - - { - const { done, value } = await stream.read(); - assert(!done); - assertEquals(decoder.decode(value), "data: hello\r\n\r\n"); - } - - await stream.cancel(); - clearInterval(timerId); - ac.abort(); - await server.finished; - }, -); - -Deno.test( - { permissions: { net: true } }, - async function serveWithPrototypePollution() { - const originalThen = Promise.prototype.then; - const originalSymbolIterator = Array.prototype[Symbol.iterator]; - try { - Promise.prototype.then = Array.prototype[Symbol.iterator] = () => { - throw new Error(); - }; - const ac = new AbortController(); - const { resolve } = Promise.withResolvers<void>(); - const server = Deno.serve({ - handler: (_req) => new Response("ok"), - hostname: "localhost", - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - ac.abort(); - await server.finished; - } finally { - Promise.prototype.then = originalThen; - Array.prototype[Symbol.iterator] = originalSymbolIterator; - } - }, -); - -// https://github.com/denoland/deno/issues/15549 -Deno.test( - { permissions: { net: true } }, - async function testIssue15549() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - let count = 0; - const server = Deno.serve({ - async onListen({ port }: { port: number }) { - const res1 = await fetch(`http://localhost:${port}/`); - assertEquals(await res1.text(), "hello world 1"); - - const res2 = await fetch(`http://localhost:${port}/`); - assertEquals(await res2.text(), "hello world 2"); - - resolve(); - ac.abort(); - }, - signal: ac.signal, - }, () => { - count++; - return new Response(`hello world ${count}`); - }); - - await promise; - await server.finished; - }, -); - -// https://github.com/denoland/deno/issues/15858 -Deno.test( - "Clone should work", - { permissions: { net: true } }, - async function httpServerCanCloneRequest() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<number>(); - - const server = Deno.serve({ - handler: async (req) => { - const cloned = req.clone(); - assertEquals(req.headers, cloned.headers); - - assertEquals(cloned.url, req.url); - assertEquals(cloned.cache, req.cache); - assertEquals(cloned.destination, req.destination); - assertEquals(cloned.headers, req.headers); - assertEquals(cloned.integrity, req.integrity); - assertEquals(cloned.isHistoryNavigation, req.isHistoryNavigation); - assertEquals(cloned.isReloadNavigation, req.isReloadNavigation); - assertEquals(cloned.keepalive, req.keepalive); - assertEquals(cloned.method, req.method); - assertEquals(cloned.mode, req.mode); - assertEquals(cloned.redirect, req.redirect); - assertEquals(cloned.referrer, req.referrer); - assertEquals(cloned.referrerPolicy, req.referrerPolicy); - - // both requests can read body - await req.text(); - await cloned.json(); - - return new Response("ok"); - }, - signal: ac.signal, - onListen: ({ port }: { port: number }) => resolve(port), - onError: createOnErrorCb(ac), - }); - - try { - const port = await promise; - const resp = await fetch(`http://localhost:${port}/`, { - headers: { connection: "close" }, - method: "POST", - body: '{"sus":true}', - }); - const text = await resp.text(); - assertEquals(text, "ok"); - } finally { - ac.abort(); - await server.finished; - } - }, -); - -// https://fetch.spec.whatwg.org/#dom-request-clone -Deno.test( - "Throw if disturbed", - { permissions: { net: true } }, - async function shouldThrowIfBodyIsUnusableDisturbed() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<number>(); - - const server = Deno.serve({ - handler: async (req) => { - await req.text(); - - try { - req.clone(); - fail(); - } catch (cloneError) { - assert(cloneError instanceof TypeError); - assert( - cloneError.message.endsWith("Body is unusable."), - ); - - ac.abort(); - await server.finished; - } - - return new Response("ok"); - }, - signal: ac.signal, - onListen: ({ port }: { port: number }) => resolve(port), - }); - - try { - const port = await promise; - await fetch(`http://localhost:${port}/`, { - headers: { connection: "close" }, - method: "POST", - body: '{"bar":true}', - }); - fail(); - } catch (clientError) { - assert(clientError instanceof TypeError); - assert( - clientError.message.endsWith( - "connection closed before message completed", - ), - ); - } finally { - ac.abort(); - await server.finished; - } - }, -); - -// https://fetch.spec.whatwg.org/#dom-request-clone -Deno.test({ - name: "Throw if locked", - permissions: { net: true }, - fn: async function shouldThrowIfBodyIsUnusableLocked() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<number>(); - - const server = Deno.serve({ - handler: async (req) => { - const _reader = req.body?.getReader(); - - try { - req.clone(); - fail(); - } catch (cloneError) { - assert(cloneError instanceof TypeError); - assert( - cloneError.message.endsWith("Body is unusable."), - ); - - ac.abort(); - await server.finished; - } - return new Response("ok"); - }, - signal: ac.signal, - onListen: ({ port }: { port: number }) => resolve(port), - }); - - try { - const port = await promise; - await fetch(`http://localhost:${port}/`, { - headers: { connection: "close" }, - method: "POST", - body: '{"bar":true}', - }); - fail(); - } catch (clientError) { - assert(clientError instanceof TypeError); - assert( - clientError.message.endsWith( - "connection closed before message completed", - ), - ); - } finally { - ac.abort(); - await server.finished; - } - }, -}); - -// Checks large streaming response -// https://github.com/denoland/deno/issues/16567 -Deno.test( - { permissions: { net: true } }, - async function testIssue16567() { - const ac = new AbortController(); - const { promise, resolve } = Promise.withResolvers<void>(); - const server = Deno.serve({ - async onListen({ port }) { - const res1 = await fetch(`http://localhost:${port}/`); - assertEquals((await res1.text()).length, 40 * 50_000); - - resolve(); - ac.abort(); - }, - signal: ac.signal, - }, () => - new Response( - new ReadableStream({ - start(c) { - // 2MB "a...a" response with 40 chunks - for (const _ of Array(40)) { - c.enqueue(new Uint8Array(50_000).fill(97)); - } - c.close(); - }, - }), - )); - - await promise; - await server.finished; - }, -); - -function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader { - // Based on https://tools.ietf.org/html/rfc2616#section-19.4.6 - const tp = new TextProtoReader(r); - let finished = false; - const chunks: Array<{ - offset: number; - data: Uint8Array; - }> = []; - async function read(buf: Uint8Array): Promise<number | null> { - if (finished) return null; - const [chunk] = chunks; - if (chunk) { - const chunkRemaining = chunk.data.byteLength - chunk.offset; - const readLength = Math.min(chunkRemaining, buf.byteLength); - for (let i = 0; i < readLength; i++) { - buf[i] = chunk.data[chunk.offset + i]; - } - chunk.offset += readLength; - if (chunk.offset === chunk.data.byteLength) { - chunks.shift(); - // Consume \r\n; - if ((await tp.readLine()) === null) { - throw new Deno.errors.UnexpectedEof(); - } - } - return readLength; - } - const line = await tp.readLine(); - if (line === null) throw new Deno.errors.UnexpectedEof(); - // TODO(bartlomieju): handle chunk extension - const [chunkSizeString] = line.split(";"); - const chunkSize = parseInt(chunkSizeString, 16); - if (Number.isNaN(chunkSize) || chunkSize < 0) { - throw new Deno.errors.InvalidData("Invalid chunk size"); - } - if (chunkSize > 0) { - if (chunkSize > buf.byteLength) { - let eof = await r.readFull(buf); - if (eof === null) { - throw new Deno.errors.UnexpectedEof(); - } - const restChunk = new Uint8Array(chunkSize - buf.byteLength); - eof = await r.readFull(restChunk); - if (eof === null) { - throw new Deno.errors.UnexpectedEof(); - } else { - chunks.push({ - offset: 0, - data: restChunk, - }); - } - return buf.byteLength; - } else { - const bufToFill = buf.subarray(0, chunkSize); - const eof = await r.readFull(bufToFill); - if (eof === null) { - throw new Deno.errors.UnexpectedEof(); - } - // Consume \r\n - if ((await tp.readLine()) === null) { - throw new Deno.errors.UnexpectedEof(); - } - return chunkSize; - } - } else { - assert(chunkSize === 0); - // Consume \r\n - if ((await r.readLine()) === null) { - throw new Deno.errors.UnexpectedEof(); - } - await readTrailers(h, r); - finished = true; - return null; - } - } - return { read }; -} - -async function readTrailers( - headers: Headers, - r: BufReader, -) { - const trailers = parseTrailer(headers.get("trailer")); - if (trailers == null) return; - const trailerNames = [...trailers.keys()]; - const tp = new TextProtoReader(r); - const result = await tp.readMimeHeader(); - if (result == null) { - throw new Deno.errors.InvalidData("Missing trailer header."); - } - const undeclared = [...result.keys()].filter( - (k) => !trailerNames.includes(k), - ); - if (undeclared.length > 0) { - throw new Deno.errors.InvalidData( - `Undeclared trailers: ${Deno.inspect(undeclared)}.`, - ); - } - for (const [k, v] of result) { - headers.append(k, v); - } - const missingTrailers = trailerNames.filter((k) => !result.has(k)); - if (missingTrailers.length > 0) { - throw new Deno.errors.InvalidData( - `Missing trailers: ${Deno.inspect(missingTrailers)}.`, - ); - } - headers.delete("trailer"); -} - -function parseTrailer(field: string | null): Headers | undefined { - if (field == null) { - return undefined; - } - const trailerNames = field.split(",").map((v) => v.trim().toLowerCase()); - if (trailerNames.length === 0) { - throw new Deno.errors.InvalidData("Empty trailer header."); - } - const prohibited = trailerNames.filter((k) => isProhibitedForTrailer(k)); - if (prohibited.length > 0) { - throw new Deno.errors.InvalidData( - `Prohibited trailer names: ${Deno.inspect(prohibited)}.`, - ); - } - return new Headers(trailerNames.map((key) => [key, ""])); -} - -function isProhibitedForTrailer(key: string): boolean { - const s = new Set(["transfer-encoding", "content-length", "trailer"]); - return s.has(key.toLowerCase()); -} - -// TODO(mmastrac): curl on Windows CI stopped supporting --http2? -Deno.test( - { - permissions: { net: true, run: true }, - ignore: Deno.build.os === "windows", - }, - async function httpServeCurlH2C() { - const ac = new AbortController(); - const server = Deno.serve( - { port: servePort, signal: ac.signal }, - () => new Response("hello world!"), - ); - - assertEquals( - "hello world!", - await curlRequest([`http://localhost:${servePort}/path`]), - ); - assertEquals( - "hello world!", - await curlRequest([`http://localhost:${servePort}/path`, "--http2"]), - ); - assertEquals( - "hello world!", - await curlRequest([ - `http://localhost:${servePort}/path`, - "--http2", - "--http2-prior-knowledge", - ]), - ); - - ac.abort(); - await server.finished; - }, -); - -// TODO(mmastrac): This test should eventually use fetch, when we support trailers there. -// This test is ignored because it's flaky and relies on cURL's verbose output. -Deno.test( - { permissions: { net: true, run: true, read: true }, ignore: true }, - async function httpServerTrailers() { - const ac = new AbortController(); - const { resolve } = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: () => { - const response = new Response("Hello World", { - headers: { - "trailer": "baz", - "transfer-encoding": "chunked", - "foo": "bar", - }, - }); - addTrailers(response, [["baz", "why"]]); - return response; - }, - port: servePort, - signal: ac.signal, - onListen: onListen(resolve), - onError: createOnErrorCb(ac), - }); - - // We don't have a great way to access this right now, so just fetch the trailers with cURL - const [_, stderr] = await curlRequestWithStdErr([ - `http://localhost:${servePort}/path`, - "-v", - "--http2", - "--http2-prior-knowledge", - ]); - assertMatch(stderr, /baz: why/); - ac.abort(); - await server.finished; - }, -); - -// TODO(mmastrac): curl on CI stopped supporting --http2? -Deno.test( - { - permissions: { - net: true, - run: true, - read: true, - }, - ignore: Deno.build.os === "windows", - }, - async function httpsServeCurlH2C() { - const ac = new AbortController(); - const server = Deno.serve( - { - signal: ac.signal, - port: servePort, - cert: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.crt"), - key: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.key"), - }, - () => new Response("hello world!"), - ); - - assertEquals( - "hello world!", - await curlRequest([`https://localhost:${servePort}/path`, "-k"]), - ); - assertEquals( - "hello world!", - await curlRequest([ - `https://localhost:${servePort}/path`, - "-k", - "--http2", - ]), - ); - assertEquals( - "hello world!", - await curlRequest([ - `https://localhost:${servePort}/path`, - "-k", - "--http2", - "--http2-prior-knowledge", - ]), - ); - - ac.abort(); - await server.finished; - }, -); - -async function curlRequest(args: string[]) { - const { success, stdout, stderr } = await new Deno.Command("curl", { - args, - stdout: "piped", - stderr: "piped", - }).output(); - assert( - success, - `Failed to cURL ${args}: stdout\n\n${stdout}\n\nstderr:\n\n${stderr}`, - ); - return new TextDecoder().decode(stdout); -} - -async function curlRequestWithStdErr(args: string[]) { - const { success, stdout, stderr } = await new Deno.Command("curl", { - args, - stdout: "piped", - stderr: "piped", - }).output(); - assert( - success, - `Failed to cURL ${args}: stdout\n\n${stdout}\n\nstderr:\n\n${stderr}`, - ); - return [new TextDecoder().decode(stdout), new TextDecoder().decode(stderr)]; -} - -Deno.test("Deno.HttpServer is not thenable", async () => { - // deno-lint-ignore require-await - async function serveTest() { - const server = Deno.serve({ port: servePort }, (_) => new Response("")); - assert(!("then" in server)); - return server; - } - const server = await serveTest(); - await server.shutdown(); -}); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { run: true, read: true, write: true }, - }, - async function httpServerUnixDomainSocket() { - const { promise, resolve } = Promise.withResolvers<{ path: string }>(); - const ac = new AbortController(); - const filePath = tmpUnixSocketPath(); - const server = Deno.serve( - { - signal: ac.signal, - path: filePath, - onListen(info) { - resolve(info); - }, - onError: createOnErrorCb(ac), - }, - (_req, { remoteAddr }) => { - assertEquals(remoteAddr, { path: filePath, transport: "unix" }); - return new Response("hello world!"); - }, - ); - - assertEquals(await promise, { path: filePath }); - assertEquals( - "hello world!", - await curlRequest(["--unix-socket", filePath, "http://localhost"]), - ); - ac.abort(); - await server.finished; - }, -); - -// serve Handler must return Response class or promise that resolves Response class -Deno.test( - { permissions: { net: true, run: true } }, - async function handleServeCallbackReturn() { - const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve( - { - port: servePort, - onListen: onListen(listeningDeferred.resolve), - signal: ac.signal, - onError: (error) => { - assert(error instanceof TypeError); - assert( - error.message === - "Return value from serve handler must be a response or a promise resolving to a response", - ); - deferred.resolve(); - return new Response("Customized Internal Error from onError"); - }, - }, - () => { - // Trick the typechecker - return <Response> <unknown> undefined; - }, - ); - await listeningDeferred.promise; - const respText = await curlRequest([`http://localhost:${servePort}`]); - await deferred.promise; - ac.abort(); - await server.finished; - assert(respText === "Customized Internal Error from onError"); - }, -); - -// onError Handler must return Response class or promise that resolves Response class -Deno.test( - { permissions: { net: true, run: true } }, - async function handleServeErrorCallbackReturn() { - const { promise, resolve } = Promise.withResolvers<void>(); - const ac = new AbortController(); - - const server = Deno.serve( - { - port: servePort, - onListen: onListen(resolve), - signal: ac.signal, - onError: () => { - // Trick the typechecker - return <Response> <unknown> undefined; - }, - }, - () => { - // Trick the typechecker - return <Response> <unknown> undefined; - }, - ); - await promise; - const respText = await curlRequest([`http://localhost:${servePort}`]); - ac.abort(); - await server.finished; - assert(respText === "Internal Server Error"); - }, -); diff --git a/cli/tests/unit/signal_test.ts b/cli/tests/unit/signal_test.ts deleted file mode 100644 index 2ba2ffb15..000000000 --- a/cli/tests/unit/signal_test.ts +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals, assertThrows, delay } from "./test_util.ts"; - -Deno.test( - { ignore: Deno.build.os !== "windows" }, - function signalsNotImplemented() { - const msg = - "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK)."; - assertThrows( - () => { - Deno.addSignalListener("SIGALRM", () => {}); - }, - Error, - msg, - ); - assertThrows( - () => { - Deno.addSignalListener("SIGCHLD", () => {}); - }, - Error, - msg, - ); - assertThrows( - () => { - Deno.addSignalListener("SIGHUP", () => {}); - }, - Error, - msg, - ); - assertThrows( - () => { - Deno.addSignalListener("SIGIO", () => {}); - }, - Error, - msg, - ); - assertThrows( - () => { - Deno.addSignalListener("SIGPIPE", () => {}); - }, - Error, - msg, - ); - assertThrows( - () => { - Deno.addSignalListener("SIGQUIT", () => {}); - }, - Error, - msg, - ); - assertThrows( - () => { - Deno.addSignalListener("SIGTERM", () => {}); - }, - Error, - msg, - ); - assertThrows( - () => { - Deno.addSignalListener("SIGUSR1", () => {}); - }, - Error, - msg, - ); - assertThrows( - () => { - Deno.addSignalListener("SIGUSR2", () => {}); - }, - Error, - msg, - ); - assertThrows( - () => { - Deno.addSignalListener("SIGWINCH", () => {}); - }, - Error, - msg, - ); - assertThrows( - () => Deno.addSignalListener("SIGKILL", () => {}), - Error, - msg, - ); - assertThrows( - () => Deno.addSignalListener("SIGSTOP", () => {}), - Error, - msg, - ); - assertThrows( - () => Deno.addSignalListener("SIGILL", () => {}), - Error, - msg, - ); - assertThrows( - () => Deno.addSignalListener("SIGFPE", () => {}), - Error, - msg, - ); - assertThrows( - () => Deno.addSignalListener("SIGSEGV", () => {}), - Error, - msg, - ); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { run: true }, - }, - async function signalListenerTest() { - let c = 0; - const listener = () => { - c += 1; - }; - // This test needs to be careful that it doesn't accidentally aggregate multiple - // signals into one. Sending two or more SIGxxx before the handler can be run will - // result in signal coalescing. - Deno.addSignalListener("SIGUSR1", listener); - // Sends SIGUSR1 3 times. - for (let i = 1; i <= 3; i++) { - await delay(1); - Deno.kill(Deno.pid, "SIGUSR1"); - while (c < i) { - await delay(20); - } - } - Deno.removeSignalListener("SIGUSR1", listener); - await delay(100); - assertEquals(c, 3); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { run: true }, - }, - async function multipleSignalListenerTest() { - let c = ""; - const listener0 = () => { - c += "0"; - }; - const listener1 = () => { - c += "1"; - }; - // This test needs to be careful that it doesn't accidentally aggregate multiple - // signals into one. Sending two or more SIGxxx before the handler can be run will - // result in signal coalescing. - Deno.addSignalListener("SIGUSR2", listener0); - Deno.addSignalListener("SIGUSR2", listener1); - - // Sends SIGUSR2 3 times. - for (let i = 1; i <= 3; i++) { - await delay(1); - Deno.kill(Deno.pid, "SIGUSR2"); - while (c.length < i * 2) { - await delay(20); - } - } - - Deno.removeSignalListener("SIGUSR2", listener1); - - // Sends SIGUSR2 3 times. - for (let i = 1; i <= 3; i++) { - await delay(1); - Deno.kill(Deno.pid, "SIGUSR2"); - while (c.length < 6 + i) { - await delay(20); - } - } - - // Sends SIGUSR1 (irrelevant signal) 3 times. - for (const _ of Array(3)) { - await delay(20); - Deno.kill(Deno.pid, "SIGUSR1"); - } - - // No change - assertEquals(c, "010101000"); - - Deno.removeSignalListener("SIGUSR2", listener0); - - await delay(100); - - // The first 3 events are handled by both handlers - // The last 3 events are handled only by handler0 - assertEquals(c, "010101000"); - }, -); - -// This tests that pending op_signal_poll doesn't block the runtime from exiting the process. -Deno.test( - { - permissions: { run: true, read: true }, - }, - async function canExitWhileListeningToSignal() { - const { code } = await new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "--unstable", - "Deno.addSignalListener('SIGINT', () => {})", - ], - }).output(); - assertEquals(code, 0); - }, -); - -Deno.test( - { - ignore: Deno.build.os !== "windows", - permissions: { run: true }, - }, - function windowsThrowsOnNegativeProcessIdTest() { - assertThrows( - () => { - Deno.kill(-1, "SIGKILL"); - }, - TypeError, - "Invalid pid", - ); - }, -); - -Deno.test( - { - ignore: Deno.build.os !== "windows", - permissions: { run: true }, - }, - function noOpenSystemIdleProcessTest() { - let signal: Deno.Signal = "SIGKILL"; - - assertThrows( - () => { - Deno.kill(0, signal); - }, - TypeError, - `Invalid pid`, - ); - - signal = "SIGTERM"; - assertThrows( - () => { - Deno.kill(0, signal); - }, - TypeError, - `Invalid pid`, - ); - }, -); - -Deno.test(function signalInvalidHandlerTest() { - assertThrows(() => { - // deno-lint-ignore no-explicit-any - Deno.addSignalListener("SIGINT", "handler" as any); - }); - assertThrows(() => { - // deno-lint-ignore no-explicit-any - Deno.removeSignalListener("SIGINT", "handler" as any); - }); -}); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { run: true }, - }, - function signalForbiddenSignalTest() { - assertThrows( - () => Deno.addSignalListener("SIGKILL", () => {}), - TypeError, - "Binding to signal 'SIGKILL' is not allowed", - ); - assertThrows( - () => Deno.addSignalListener("SIGSTOP", () => {}), - TypeError, - "Binding to signal 'SIGSTOP' is not allowed", - ); - assertThrows( - () => Deno.addSignalListener("SIGILL", () => {}), - TypeError, - "Binding to signal 'SIGILL' is not allowed", - ); - assertThrows( - () => Deno.addSignalListener("SIGFPE", () => {}), - TypeError, - "Binding to signal 'SIGFPE' is not allowed", - ); - assertThrows( - () => Deno.addSignalListener("SIGSEGV", () => {}), - TypeError, - "Binding to signal 'SIGSEGV' is not allowed", - ); - }, -); diff --git a/cli/tests/unit/stat_test.ts b/cli/tests/unit/stat_test.ts deleted file mode 100644 index ccb17b164..000000000 --- a/cli/tests/unit/stat_test.ts +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertRejects, - assertThrows, - pathToAbsoluteFileUrl, -} from "./test_util.ts"; - -Deno.test({ permissions: { read: true } }, function fstatSyncSuccess() { - using file = Deno.openSync("README.md"); - const fileInfo = Deno.fstatSync(file.rid); - assert(fileInfo.isFile); - assert(!fileInfo.isSymlink); - assert(!fileInfo.isDirectory); - assert(fileInfo.size); - assert(fileInfo.atime); - assert(fileInfo.mtime); - // The `birthtime` field is not available on Linux before kernel version 4.11. - assert(fileInfo.birthtime || Deno.build.os === "linux"); -}); - -Deno.test({ permissions: { read: true } }, async function fstatSuccess() { - using file = await Deno.open("README.md"); - const fileInfo = await Deno.fstat(file.rid); - assert(fileInfo.isFile); - assert(!fileInfo.isSymlink); - assert(!fileInfo.isDirectory); - assert(fileInfo.size); - assert(fileInfo.atime); - assert(fileInfo.mtime); - // The `birthtime` field is not available on Linux before kernel version 4.11. - assert(fileInfo.birthtime || Deno.build.os === "linux"); -}); - -Deno.test( - { permissions: { read: true, write: true } }, - function statSyncSuccess() { - const readmeInfo = Deno.statSync("README.md"); - assert(readmeInfo.isFile); - assert(!readmeInfo.isSymlink); - - const modulesInfo = Deno.statSync("cli/tests/testdata/symlink_to_subdir"); - assert(modulesInfo.isDirectory); - assert(!modulesInfo.isSymlink); - - const testsInfo = Deno.statSync("cli/tests"); - assert(testsInfo.isDirectory); - assert(!testsInfo.isSymlink); - - const tempFile = Deno.makeTempFileSync(); - const tempInfo = Deno.statSync(tempFile); - let now = Date.now(); - assert(tempInfo.atime !== null && now - tempInfo.atime.valueOf() < 1000); - assert(tempInfo.mtime !== null && now - tempInfo.mtime.valueOf() < 1000); - assert( - tempInfo.birthtime === null || now - tempInfo.birthtime.valueOf() < 1000, - ); - - const readmeInfoByUrl = Deno.statSync(pathToAbsoluteFileUrl("README.md")); - assert(readmeInfoByUrl.isFile); - assert(!readmeInfoByUrl.isSymlink); - - const modulesInfoByUrl = Deno.statSync( - pathToAbsoluteFileUrl("cli/tests/testdata/symlink_to_subdir"), - ); - assert(modulesInfoByUrl.isDirectory); - assert(!modulesInfoByUrl.isSymlink); - - const testsInfoByUrl = Deno.statSync(pathToAbsoluteFileUrl("cli/tests")); - assert(testsInfoByUrl.isDirectory); - assert(!testsInfoByUrl.isSymlink); - - const tempFileForUrl = Deno.makeTempFileSync(); - const tempInfoByUrl = Deno.statSync( - new URL( - `file://${Deno.build.os === "windows" ? "/" : ""}${tempFileForUrl}`, - ), - ); - now = Date.now(); - assert( - tempInfoByUrl.atime !== null && - now - tempInfoByUrl.atime.valueOf() < 1000, - ); - assert( - tempInfoByUrl.mtime !== null && - now - tempInfoByUrl.mtime.valueOf() < 1000, - ); - assert( - tempInfoByUrl.birthtime === null || - now - tempInfoByUrl.birthtime.valueOf() < 1000, - ); - - Deno.removeSync(tempFile, { recursive: true }); - Deno.removeSync(tempFileForUrl, { recursive: true }); - }, -); - -Deno.test({ permissions: { read: false } }, function statSyncPerm() { - assertThrows(() => { - Deno.statSync("README.md"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, function statSyncNotFound() { - assertThrows( - () => { - Deno.statSync("bad_file_name"); - }, - Deno.errors.NotFound, - `stat 'bad_file_name'`, - ); -}); - -Deno.test({ permissions: { read: true } }, function lstatSyncSuccess() { - const packageInfo = Deno.lstatSync("README.md"); - assert(packageInfo.isFile); - assert(!packageInfo.isSymlink); - - const packageInfoByUrl = Deno.lstatSync(pathToAbsoluteFileUrl("README.md")); - assert(packageInfoByUrl.isFile); - assert(!packageInfoByUrl.isSymlink); - - const modulesInfo = Deno.lstatSync("cli/tests/testdata/symlink_to_subdir"); - assert(!modulesInfo.isDirectory); - assert(modulesInfo.isSymlink); - - const modulesInfoByUrl = Deno.lstatSync( - pathToAbsoluteFileUrl("cli/tests/testdata/symlink_to_subdir"), - ); - assert(!modulesInfoByUrl.isDirectory); - assert(modulesInfoByUrl.isSymlink); - - const coreInfo = Deno.lstatSync("cli"); - assert(coreInfo.isDirectory); - assert(!coreInfo.isSymlink); - - const coreInfoByUrl = Deno.lstatSync(pathToAbsoluteFileUrl("cli")); - assert(coreInfoByUrl.isDirectory); - assert(!coreInfoByUrl.isSymlink); -}); - -Deno.test({ permissions: { read: false } }, function lstatSyncPerm() { - assertThrows(() => { - Deno.lstatSync("assets/hello.txt"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, function lstatSyncNotFound() { - assertThrows( - () => { - Deno.lstatSync("bad_file_name"); - }, - Deno.errors.NotFound, - `stat 'bad_file_name'`, - ); -}); - -Deno.test( - { permissions: { read: true, write: true } }, - async function statSuccess() { - const readmeInfo = await Deno.stat("README.md"); - assert(readmeInfo.isFile); - assert(!readmeInfo.isSymlink); - - const readmeInfoByUrl = await Deno.stat( - pathToAbsoluteFileUrl("README.md"), - ); - assert(readmeInfoByUrl.isFile); - assert(!readmeInfoByUrl.isSymlink); - - const modulesInfo = await Deno.stat("cli/tests/testdata/symlink_to_subdir"); - assert(modulesInfo.isDirectory); - assert(!modulesInfo.isSymlink); - - const modulesInfoByUrl = await Deno.stat( - pathToAbsoluteFileUrl("cli/tests/testdata/symlink_to_subdir"), - ); - assert(modulesInfoByUrl.isDirectory); - assert(!modulesInfoByUrl.isSymlink); - - const testsInfo = await Deno.stat("cli/tests"); - assert(testsInfo.isDirectory); - assert(!testsInfo.isSymlink); - - const testsInfoByUrl = await Deno.stat(pathToAbsoluteFileUrl("cli/tests")); - assert(testsInfoByUrl.isDirectory); - assert(!testsInfoByUrl.isSymlink); - - const tempFile = await Deno.makeTempFile(); - const tempInfo = await Deno.stat(tempFile); - let now = Date.now(); - assert(tempInfo.atime !== null && now - tempInfo.atime.valueOf() < 1000); - assert(tempInfo.mtime !== null && now - tempInfo.mtime.valueOf() < 1000); - - assert( - tempInfo.birthtime === null || now - tempInfo.birthtime.valueOf() < 1000, - ); - - const tempFileForUrl = await Deno.makeTempFile(); - const tempInfoByUrl = await Deno.stat( - new URL( - `file://${Deno.build.os === "windows" ? "/" : ""}${tempFileForUrl}`, - ), - ); - now = Date.now(); - assert( - tempInfoByUrl.atime !== null && - now - tempInfoByUrl.atime.valueOf() < 1000, - ); - assert( - tempInfoByUrl.mtime !== null && - now - tempInfoByUrl.mtime.valueOf() < 1000, - ); - assert( - tempInfoByUrl.birthtime === null || - now - tempInfoByUrl.birthtime.valueOf() < 1000, - ); - - Deno.removeSync(tempFile, { recursive: true }); - Deno.removeSync(tempFileForUrl, { recursive: true }); - }, -); - -Deno.test({ permissions: { read: false } }, async function statPerm() { - await assertRejects(async () => { - await Deno.stat("README.md"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, async function statNotFound() { - await assertRejects( - async () => { - await Deno.stat("bad_file_name"); - }, - Deno.errors.NotFound, - `stat 'bad_file_name'`, - ); -}); - -Deno.test({ permissions: { read: true } }, async function lstatSuccess() { - const readmeInfo = await Deno.lstat("README.md"); - assert(readmeInfo.isFile); - assert(!readmeInfo.isSymlink); - - const readmeInfoByUrl = await Deno.lstat(pathToAbsoluteFileUrl("README.md")); - assert(readmeInfoByUrl.isFile); - assert(!readmeInfoByUrl.isSymlink); - - const modulesInfo = await Deno.lstat("cli/tests/testdata/symlink_to_subdir"); - assert(!modulesInfo.isDirectory); - assert(modulesInfo.isSymlink); - - const modulesInfoByUrl = await Deno.lstat( - pathToAbsoluteFileUrl("cli/tests/testdata/symlink_to_subdir"), - ); - assert(!modulesInfoByUrl.isDirectory); - assert(modulesInfoByUrl.isSymlink); - - const coreInfo = await Deno.lstat("cli"); - assert(coreInfo.isDirectory); - assert(!coreInfo.isSymlink); - - const coreInfoByUrl = await Deno.lstat(pathToAbsoluteFileUrl("cli")); - assert(coreInfoByUrl.isDirectory); - assert(!coreInfoByUrl.isSymlink); -}); - -Deno.test({ permissions: { read: false } }, async function lstatPerm() { - await assertRejects(async () => { - await Deno.lstat("README.md"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { read: true } }, async function lstatNotFound() { - await assertRejects( - async () => { - await Deno.lstat("bad_file_name"); - }, - Deno.errors.NotFound, - `stat 'bad_file_name'`, - ); -}); - -Deno.test( - { - ignore: Deno.build.os !== "windows", - permissions: { read: true, write: true }, - }, - function statNoUnixFields() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const tempDir = Deno.makeTempDirSync(); - const filename = tempDir + "/test.txt"; - Deno.writeFileSync(filename, data, { mode: 0o666 }); - const s = Deno.statSync(filename); - assert(s.dev !== 0); - assert(s.ino === null); - assert(s.mode === null); - assert(s.nlink === null); - assert(s.uid === null); - assert(s.gid === null); - assert(s.rdev === null); - assert(s.blksize === null); - assert(s.blocks === null); - assert(s.isBlockDevice === null); - assert(s.isCharDevice === null); - assert(s.isFifo === null); - assert(s.isSocket === null); - }, -); - -Deno.test( - { - ignore: Deno.build.os === "windows", - permissions: { read: true, write: true }, - }, - function statUnixFields() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const tempDir = Deno.makeTempDirSync(); - const filename = tempDir + "/test.txt"; - const filename2 = tempDir + "/test2.txt"; - Deno.writeFileSync(filename, data, { mode: 0o666 }); - // Create a link - Deno.linkSync(filename, filename2); - const s = Deno.statSync(filename); - assert(s.dev !== null); - assert(s.ino !== null); - assertEquals(s.mode! & 0o666, 0o666); - assertEquals(s.nlink, 2); - assert(s.uid !== null); - assert(s.gid !== null); - assert(s.rdev !== null); - assert(s.blksize !== null); - assert(s.blocks !== null); - assert(!s.isBlockDevice); - assert(!s.isCharDevice); - assert(!s.isFifo); - assert(!s.isSocket); - }, -); diff --git a/cli/tests/unit/stdio_test.ts b/cli/tests/unit/stdio_test.ts deleted file mode 100644 index d24fdc8ef..000000000 --- a/cli/tests/unit/stdio_test.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "./test_util.ts"; - -Deno.test(async function stdioStdinRead() { - const nread = await Deno.stdin.read(new Uint8Array(0)); - assertEquals(nread, 0); -}); - -Deno.test(function stdioStdinReadSync() { - const nread = Deno.stdin.readSync(new Uint8Array(0)); - assertEquals(nread, 0); -}); - -Deno.test(async function stdioStdoutWrite() { - const nwritten = await Deno.stdout.write(new Uint8Array(0)); - assertEquals(nwritten, 0); -}); - -Deno.test(function stdioStdoutWriteSync() { - const nwritten = Deno.stdout.writeSync(new Uint8Array(0)); - assertEquals(nwritten, 0); -}); - -Deno.test(async function stdioStderrWrite() { - const nwritten = await Deno.stderr.write(new Uint8Array(0)); - assertEquals(nwritten, 0); -}); - -Deno.test(function stdioStderrWriteSync() { - const nwritten = Deno.stderr.writeSync(new Uint8Array(0)); - assertEquals(nwritten, 0); -}); diff --git a/cli/tests/unit/streams_test.ts b/cli/tests/unit/streams_test.ts deleted file mode 100644 index 6db9f666c..000000000 --- a/cli/tests/unit/streams_test.ts +++ /dev/null @@ -1,478 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals, fail } 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 -function helloWorldStream( - close?: boolean, - cancelResolve?: (value: unknown) => void, -) { - return new ReadableStream({ - start(controller) { - controller.enqueue("hello, world"); - if (close == true) { - controller.close(); - } - }, - cancel(reason) { - if (cancelResolve != undefined) { - cancelResolve(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()); -} - -// Long stream with Lorem Ipsum text. -function longAsyncStream(cancelResolve?: (value: unknown) => void) { - let currentTimeout: number | undefined = undefined; - return new ReadableStream({ - async start(controller) { - for (let i = 0; i < 100; i++) { - await new Promise((r) => currentTimeout = setTimeout(r, 1)); - currentTimeout = undefined; - controller.enqueue(LOREM); - } - controller.close(); - }, - cancel(reason) { - if (cancelResolve != undefined) { - cancelResolve(reason); - } - if (currentTimeout !== undefined) { - clearTimeout(currentTimeout); - } - }, - }).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()); -} - -function largePacketStream(packetSize: number, count: number) { - return new ReadableStream({ - pull(controller) { - if (count-- > 0) { - const buffer = new Uint8Array(packetSize); - for (let i = 0; i < 256; i++) { - buffer[i * (packetSize / 256)] = i; - } - controller.enqueue(buffer); - } else { - controller.close(); - } - }, - }); -} - -// 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(); - }, - }); -} - -// Try to blow up any recursive reads. -function veryLongTinyPacketStream(length: number) { - return new ReadableStream({ - start(controller) { - for (let i = 0; i < length; i++) { - controller.enqueue(new Uint8Array([1])); - } - 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.read(rid, buffer); - assertEquals(nread, 12); - core.close(rid); -}); - -// Close the stream after reading everything -Deno.test(async function readableStreamClose() { - const cancel = Promise.withResolvers(); - const rid = resourceForReadableStream( - helloWorldStream(false, cancel.resolve), - ); - const buffer = new Uint8Array(1024); - const nread = await core.read(rid, buffer); - assertEquals(nread, 12); - core.close(rid); - assertEquals(await cancel.promise, "resource closed"); -}); - -// Close the stream without reading everything -Deno.test(async function readableStreamClosePartialRead() { - const cancel = Promise.withResolvers(); - const rid = resourceForReadableStream( - helloWorldStream(false, cancel.resolve), - ); - const buffer = new Uint8Array(5); - const nread = await core.read(rid, buffer); - assertEquals(nread, 5); - core.close(rid); - assertEquals(await cancel.promise, "resource closed"); -}); - -// Close the stream without reading anything -Deno.test(async function readableStreamCloseWithoutRead() { - const cancel = Promise.withResolvers(); - const rid = resourceForReadableStream( - helloWorldStream(false, cancel.resolve), - ); - core.close(rid); - assertEquals(await cancel.promise, "resource closed"); -}); - -// Close the stream without reading anything -Deno.test(async function readableStreamCloseWithoutRead2() { - const cancel = Promise.withResolvers(); - const rid = resourceForReadableStream(longAsyncStream(cancel.resolve)); - core.close(rid); - assertEquals(await cancel.promise, "resource closed"); -}); - -Deno.test(async function readableStreamPartial() { - const rid = resourceForReadableStream(helloWorldStream()); - const buffer = new Uint8Array(5); - const nread = await core.read(rid, buffer); - assertEquals(nread, 5); - const buffer2 = new Uint8Array(1024); - const nread2 = await core.read(rid, buffer2); - assertEquals(nread2, 7); - core.close(rid); -}); - -Deno.test(async function readableStreamLongReadAll() { - const rid = resourceForReadableStream(longStream()); - const buffer = await core.readAll(rid); - assertEquals(buffer.length, LOREM.length * 4); - core.close(rid); -}); - -Deno.test(async function readableStreamLongAsyncReadAll() { - const rid = resourceForReadableStream(longAsyncStream()); - const buffer = await core.readAll(rid); - assertEquals(buffer.length, LOREM.length * 100); - core.close(rid); -}); - -Deno.test(async function readableStreamVeryLongReadAll() { - const rid = resourceForReadableStream(veryLongTinyPacketStream(1_000_000)); - const buffer = await core.readAll(rid); - assertEquals(buffer.length, 1_000_000); - core.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.read(rid, new Uint8Array(16)); - total += length; - if (length == 0) { - break; - } - } - assertEquals(total, LOREM.length * 4); - core.close(rid); -}); - -for ( - const type of [ - "string", - "TypeError", - "controller", - ] as ("string" | "TypeError" | "controller")[] -) { - Deno.test(`readableStreamError_${type}`, async function () { - const rid = resourceForReadableStream(errorStream(type)); - let nread; - try { - nread = await core.read(rid, new Uint8Array(16)); - } catch (_) { - fail("Should not have thrown"); - } - assertEquals(12, nread); - try { - await core.read(rid, new Uint8Array(1)); - fail(); - } catch (e) { - assertEquals(e.message, `Uh oh (${type})!`); - } - core.close(rid); - }); -} - -Deno.test(async function readableStreamEmptyOnStart() { - const rid = resourceForReadableStream(emptyStream(true)); - const buffer = new Uint8Array(1024); - const nread = await core.read(rid, buffer); - assertEquals(nread, 0); - core.close(rid); -}); - -Deno.test(async function readableStreamEmptyOnPull() { - const rid = resourceForReadableStream(emptyStream(false)); - const buffer = new Uint8Array(1024); - const nread = await core.read(rid, buffer); - assertEquals(nread, 0); - core.close(rid); -}); - -Deno.test(async function readableStreamEmptyReadAll() { - const rid = resourceForReadableStream(emptyStream(false)); - const buffer = await core.readAll(rid); - assertEquals(buffer.length, 0); - core.close(rid); -}); - -Deno.test(async function readableStreamWithEmptyChunk() { - const rid = resourceForReadableStream(emptyChunkStream()); - const buffer = await core.readAll(rid); - assertEquals(buffer, new Uint8Array([1, 2])); - core.close(rid); -}); - -Deno.test(async function readableStreamWithEmptyChunkOneByOne() { - const rid = resourceForReadableStream(emptyChunkStream()); - assertEquals(1, await core.read(rid, new Uint8Array(1))); - assertEquals(1, await core.read(rid, new Uint8Array(1))); - assertEquals(0, await core.read(rid, new Uint8Array(1))); - core.close(rid); -}); - -// Ensure that we correctly transmit all the sub-chunks of the larger chunks. -Deno.test(async function readableStreamReadSmallerChunks() { - const packetSize = 16 * 1024; - const rid = resourceForReadableStream(largePacketStream(packetSize, 1)); - const buffer = new Uint8Array(packetSize); - for (let i = 0; i < packetSize / 1024; i++) { - await core.read(rid, buffer.subarray(i * 1024, i * 1024 + 1024)); - } - for (let i = 0; i < 256; i++) { - assertEquals( - i, - buffer[i * (packetSize / 256)], - `at index ${i * (packetSize / 256)}`, - ); - } - core.close(rid); -}); - -Deno.test(async function readableStreamLargePackets() { - const packetSize = 128 * 1024; - const rid = resourceForReadableStream(largePacketStream(packetSize, 1024)); - for (let i = 0; i < 1024; i++) { - const buffer = new Uint8Array(packetSize); - assertEquals(packetSize, await core.read(rid, buffer)); - for (let i = 0; i < 256; i++) { - assertEquals( - i, - buffer[i * (packetSize / 256)], - `at index ${i * (packetSize / 256)}`, - ); - } - } - assertEquals(0, await core.read(rid, new Uint8Array(1))); - core.close(rid); -}); - -Deno.test(async function readableStreamVeryLargePackets() { - // 1024 packets of 1MB - const rid = resourceForReadableStream(largePacketStream(1024 * 1024, 1024)); - let total = 0; - // Read 96kB up to 12,288 times (96kB is not an even multiple of the 1MB packet size to test this) - const readCounts: Record<number, number> = {}; - for (let i = 0; i < 12 * 1024; i++) { - const nread = await core.read(rid, new Uint8Array(96 * 1024)); - total += nread; - readCounts[nread] = (readCounts[nread] || 0) + 1; - if (nread == 0) { - break; - } - } - assertEquals({ 0: 1, 65536: 1024, 98304: 10 * 1024 }, readCounts); - assertEquals(total, 1024 * 1024 * 1024); - core.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.read(rid, buffer); - } - if (action == "Throw") { - try { - const buffer = new Uint8Array(1); - assertEquals(1, await core.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.read(rid, buffer)); - } - } finally { - core.close(rid); - } - }); -} - -// 1024 is the size of the internal packet buffer -- we want to make sure we fill the internal pipe fully. -for (const packetCount of [1, 1024]) { - Deno.test(`readableStreamWithAggressiveResourceClose_${packetCount}`, async function () { - let first = true; - const { promise, resolve } = Promise.withResolvers(); - const rid = resourceForReadableStream( - new ReadableStream({ - pull(controller) { - if (first) { - // We queue this up and then immediately close the resource (not the reader) - for (let i = 0; i < packetCount; i++) { - controller.enqueue(new Uint8Array(1)); - } - core.close(rid); - // This doesn't throw, even though the resource is closed - controller.enqueue(new Uint8Array(1)); - first = false; - } - }, - cancel(reason) { - resolve(reason); - }, - }), - ); - try { - for (let i = 0; i < packetCount; i++) { - await core.read(rid, new Uint8Array(1)); - } - fail(); - } catch (e) { - assertEquals(e.message, "operation canceled"); - } - assertEquals(await promise, "resource closed"); - }); -} diff --git a/cli/tests/unit/structured_clone_test.ts b/cli/tests/unit/structured_clone_test.ts deleted file mode 100644 index 314a276dd..000000000 --- a/cli/tests/unit/structured_clone_test.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assert, assertEquals, assertThrows } from "./test_util.ts"; - -// Basic tests for the structured clone algorithm. Mainly tests TypeScript -// typings. Actual functionality is tested in WPT. - -Deno.test("self.structuredClone", async () => { - const arrayOriginal = ["hello world"]; - const channelOriginal = new MessageChannel(); - const [arrayCloned, portTransferred] = self - .structuredClone([arrayOriginal, channelOriginal.port2], { - transfer: [channelOriginal.port2], - }); - assert(arrayOriginal !== arrayCloned); // not the same identity - assertEquals(arrayCloned, arrayOriginal); // but same value - channelOriginal.port1.postMessage("1"); - await new Promise((resolve) => portTransferred.onmessage = () => resolve(1)); - channelOriginal.port1.close(); - portTransferred.close(); -}); - -Deno.test("correct DataCloneError message", () => { - assertThrows( - () => { - const sab = new SharedArrayBuffer(1024); - structuredClone(sab, { transfer: [sab] }); - }, - DOMException, - "Value not transferable", - ); - - const ab = new ArrayBuffer(1); - // detach ArrayBuffer - structuredClone(ab, { transfer: [ab] }); - assertThrows( - () => { - structuredClone(ab, { transfer: [ab] }); - }, - DOMException, - "ArrayBuffer at index 0 is already detached", - ); - - const ab2 = new ArrayBuffer(0); - assertThrows( - () => { - structuredClone([ab2, ab], { transfer: [ab2, ab] }); - }, - DOMException, - "ArrayBuffer at index 1 is already detached", - ); - - // ab2 should not be detached after above failure - structuredClone(ab2, { transfer: [ab2] }); -}); diff --git a/cli/tests/unit/symbol_test.ts b/cli/tests/unit/symbol_test.ts deleted file mode 100644 index 54db7f5ba..000000000 --- a/cli/tests/unit/symbol_test.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert } from "./test_util.ts"; - -// Test that `Symbol.metadata` is defined. This file can be removed when V8 -// supports `Symbol.metadata` natively. - -Deno.test( - function symbolMetadataIsDefined() { - assert(typeof Symbol.metadata === "symbol"); - }, -); diff --git a/cli/tests/unit/symlink_test.ts b/cli/tests/unit/symlink_test.ts deleted file mode 100644 index 310c36930..000000000 --- a/cli/tests/unit/symlink_test.ts +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertRejects, - assertThrows, - pathToAbsoluteFileUrl, -} from "./test_util.ts"; - -Deno.test( - { permissions: { read: true, write: true } }, - function symlinkSyncSuccess() { - const testDir = Deno.makeTempDirSync(); - const oldname = testDir + "/oldname"; - const newname = testDir + "/newname"; - Deno.mkdirSync(oldname); - Deno.symlinkSync(oldname, newname); - const newNameInfoLStat = Deno.lstatSync(newname); - const newNameInfoStat = Deno.statSync(newname); - assert(newNameInfoLStat.isSymlink); - assert(newNameInfoStat.isDirectory); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function symlinkSyncURL() { - const testDir = Deno.makeTempDirSync(); - const oldname = testDir + "/oldname"; - const newname = testDir + "/newname"; - Deno.mkdirSync(oldname); - Deno.symlinkSync( - pathToAbsoluteFileUrl(oldname), - pathToAbsoluteFileUrl(newname), - ); - const newNameInfoLStat = Deno.lstatSync(newname); - const newNameInfoStat = Deno.statSync(newname); - assert(newNameInfoLStat.isSymlink); - assert(newNameInfoStat.isDirectory); - }, -); - -Deno.test( - { permissions: { read: false, write: false } }, - function symlinkSyncPerm() { - assertThrows(() => { - Deno.symlinkSync("oldbaddir", "newbaddir"); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function symlinkSyncAlreadyExist() { - const existingFile = Deno.makeTempFileSync(); - const existingFile2 = Deno.makeTempFileSync(); - assertThrows( - () => { - Deno.symlinkSync(existingFile, existingFile2); - }, - Deno.errors.AlreadyExists, - `symlink '${existingFile}' -> '${existingFile2}'`, - ); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function symlinkSuccess() { - const testDir = Deno.makeTempDirSync(); - const oldname = testDir + "/oldname"; - const newname = testDir + "/newname"; - Deno.mkdirSync(oldname); - await Deno.symlink(oldname, newname); - const newNameInfoLStat = Deno.lstatSync(newname); - const newNameInfoStat = Deno.statSync(newname); - assert(newNameInfoLStat.isSymlink, "NOT SYMLINK"); - assert(newNameInfoStat.isDirectory, "NOT DIRECTORY"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function symlinkURL() { - const testDir = Deno.makeTempDirSync(); - const oldname = testDir + "/oldname"; - const newname = testDir + "/newname"; - Deno.mkdirSync(oldname); - await Deno.symlink( - pathToAbsoluteFileUrl(oldname), - pathToAbsoluteFileUrl(newname), - ); - const newNameInfoLStat = Deno.lstatSync(newname); - const newNameInfoStat = Deno.statSync(newname); - assert(newNameInfoLStat.isSymlink, "NOT SYMLINK"); - assert(newNameInfoStat.isDirectory, "NOT DIRECTORY"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function symlinkAlreadyExist() { - const existingFile = Deno.makeTempFileSync(); - const existingFile2 = Deno.makeTempFileSync(); - await assertRejects( - async () => { - await Deno.symlink(existingFile, existingFile2); - }, - Deno.errors.AlreadyExists, - `symlink '${existingFile}' -> '${existingFile2}'`, - ); - }, -); - -Deno.test( - { permissions: { read: true, write: ["."] } }, - async function symlinkNoFullWritePermissions() { - await assertRejects( - () => Deno.symlink("old", "new"), - Deno.errors.PermissionDenied, - ); - assertThrows( - () => Deno.symlinkSync("old", "new"), - Deno.errors.PermissionDenied, - ); - }, -); - -Deno.test( - { permissions: { read: ["."], write: true } }, - async function symlinkNoFullReadPermissions() { - await assertRejects( - () => Deno.symlink("old", "new"), - Deno.errors.PermissionDenied, - ); - assertThrows( - () => Deno.symlinkSync("old", "new"), - Deno.errors.PermissionDenied, - ); - }, -); diff --git a/cli/tests/unit/sync_test.ts b/cli/tests/unit/sync_test.ts deleted file mode 100644 index 40a8054c0..000000000 --- a/cli/tests/unit/sync_test.ts +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "./test_util.ts"; - -Deno.test( - { permissions: { read: true, write: true } }, - function fdatasyncSyncSuccess() { - const filename = Deno.makeTempDirSync() + "/test_fdatasyncSync.txt"; - using file = Deno.openSync(filename, { - read: true, - write: true, - create: true, - }); - const data = new Uint8Array(64); - Deno.writeSync(file.rid, data); - Deno.fdatasyncSync(file.rid); - Deno.removeSync(filename); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function fdatasyncSuccess() { - const filename = (await Deno.makeTempDir()) + "/test_fdatasync.txt"; - using file = await Deno.open(filename, { - read: true, - write: true, - create: true, - }); - const data = new Uint8Array(64); - await file.write(data); - await Deno.fdatasync(file.rid); - assertEquals(await Deno.readFile(filename), data); - await Deno.remove(filename); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function fsyncSyncSuccess() { - const filename = Deno.makeTempDirSync() + "/test_fsyncSync.txt"; - using file = Deno.openSync(filename, { - read: true, - write: true, - create: true, - }); - const size = 64; - file.truncateSync(size); - Deno.fsyncSync(file.rid); - assertEquals(Deno.statSync(filename).size, size); - Deno.removeSync(filename); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function fsyncSuccess() { - const filename = (await Deno.makeTempDir()) + "/test_fsync.txt"; - using file = await Deno.open(filename, { - read: true, - write: true, - create: true, - }); - const size = 64; - await file.truncate(size); - await Deno.fsync(file.rid); - assertEquals((await Deno.stat(filename)).size, size); - await Deno.remove(filename); - }, -); diff --git a/cli/tests/unit/test_util.ts b/cli/tests/unit/test_util.ts deleted file mode 100644 index 2f2730794..000000000 --- a/cli/tests/unit/test_util.ts +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import * as colors from "@test_util/std/fmt/colors.ts"; -export { colors }; -import { join, resolve } from "@test_util/std/path/mod.ts"; -export { - assert, - assertEquals, - assertFalse, - AssertionError, - assertIsError, - assertMatch, - assertNotEquals, - assertNotStrictEquals, - assertRejects, - assertStrictEquals, - assertStringIncludes, - assertThrows, - fail, - unimplemented, - unreachable, -} from "@test_util/std/assert/mod.ts"; -export { delay } from "@test_util/std/async/delay.ts"; -export { readLines } from "@test_util/std/io/read_lines.ts"; -export { parse as parseArgs } from "@test_util/std/flags/mod.ts"; - -export function pathToAbsoluteFileUrl(path: string): URL { - path = resolve(path); - - return new URL(`file://${Deno.build.os === "windows" ? "/" : ""}${path}`); -} - -export function execCode(code: string): Promise<readonly [number, string]> { - return execCode2(code).finished(); -} - -export function execCode2(code: string) { - const command = new Deno.Command(Deno.execPath(), { - args: [ - "eval", - "--unstable", - "--no-check", - code, - ], - stdout: "piped", - stderr: "inherit", - }); - - const child = command.spawn(); - const stdout = child.stdout.pipeThrough(new TextDecoderStream()).getReader(); - let output = ""; - - return { - async waitStdoutText(text: string) { - while (true) { - const readData = await stdout.read(); - if (readData.value) { - output += readData.value; - if (output.includes(text)) { - return; - } - } - if (readData.done) { - throw new Error(`Did not find text '${text}' in stdout.`); - } - } - }, - async finished() { - while (true) { - const readData = await stdout.read(); - if (readData.value) { - output += readData.value; - } - if (readData.done) { - break; - } - } - const status = await child.status; - return [status.code, output] as const; - }, - }; -} - -export function tmpUnixSocketPath(): string { - const folder = Deno.makeTempDirSync(); - return join(folder, "socket"); -} diff --git a/cli/tests/unit/testing_test.ts b/cli/tests/unit/testing_test.ts deleted file mode 100644 index e04ab921c..000000000 --- a/cli/tests/unit/testing_test.ts +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals, assertRejects, assertThrows } from "./test_util.ts"; - -Deno.test(function testWrongOverloads() { - assertThrows( - () => { - // @ts-ignore Testing invalid overloads - Deno.test("some name", { fn: () => {} }, () => {}); - }, - TypeError, - "Unexpected 'fn' field in options, test function is already provided as the third argument.", - ); - assertThrows( - () => { - // @ts-ignore Testing invalid overloads - Deno.test("some name", { name: "some name2" }, () => {}); - }, - TypeError, - "Unexpected 'name' field in options, test name is already provided as the first argument.", - ); - assertThrows( - () => { - // @ts-ignore Testing invalid overloads - Deno.test(() => {}); - }, - TypeError, - "The test function must have a name", - ); - assertThrows( - () => { - // @ts-ignore Testing invalid overloads - Deno.test(function foo() {}, {}); - }, - TypeError, - "Unexpected second argument to Deno.test()", - ); - assertThrows( - () => { - // @ts-ignore Testing invalid overloads - Deno.test({ fn: () => {} }, function foo() {}); - }, - TypeError, - "Unexpected 'fn' field in options, test function is already provided as the second argument.", - ); - assertThrows( - () => { - // @ts-ignore Testing invalid overloads - Deno.test({}); - }, - TypeError, - "Expected 'fn' field in the first argument to be a test function.", - ); - assertThrows( - () => { - // @ts-ignore Testing invalid overloads - Deno.test({ fn: "boo!" }); - }, - TypeError, - "Expected 'fn' field in the first argument to be a test function.", - ); -}); - -Deno.test(function nameOfTestCaseCantBeEmpty() { - assertThrows( - () => { - Deno.test("", () => {}); - }, - TypeError, - "The test name can't be empty", - ); - assertThrows( - () => { - Deno.test({ - name: "", - fn: () => {}, - }); - }, - TypeError, - "The test name can't be empty", - ); -}); - -Deno.test(async function invalidStepArguments(t) { - await assertRejects( - async () => { - // deno-lint-ignore no-explicit-any - await (t as any).step("test"); - }, - TypeError, - "Expected function for second argument.", - ); - - await assertRejects( - async () => { - // deno-lint-ignore no-explicit-any - await (t as any).step("test", "not a function"); - }, - TypeError, - "Expected function for second argument.", - ); - - await assertRejects( - async () => { - // deno-lint-ignore no-explicit-any - await (t as any).step(); - }, - TypeError, - "Expected a test definition or name and function.", - ); - - await assertRejects( - async () => { - // deno-lint-ignore no-explicit-any - await (t as any).step(() => {}); - }, - TypeError, - "The step function must have a name.", - ); -}); - -Deno.test(async function nameOnTextContext(t1) { - await assertEquals(t1.name, "nameOnTextContext"); - await t1.step("step", async (t2) => { - await assertEquals(t2.name, "step"); - await t2.step("nested step", async (t3) => { - await assertEquals(t3.name, "nested step"); - }); - }); -}); - -Deno.test(async function originOnTextContext(t1) { - await assertEquals(t1.origin, Deno.mainModule); - await t1.step("step", async (t2) => { - await assertEquals(t2.origin, Deno.mainModule); - await t2.step("nested step", async (t3) => { - await assertEquals(t3.origin, Deno.mainModule); - }); - }); -}); - -Deno.test(async function parentOnTextContext(t1) { - await assertEquals(t1.parent, undefined); - await t1.step("step", async (t2) => { - await assertEquals(t1, t2.parent); - await t2.step("nested step", async (t3) => { - await assertEquals(t2, t3.parent); - }); - }); -}); - -Deno.test("explicit undefined for boolean options", { - ignore: undefined, - only: undefined, -}, () => {}); diff --git a/cli/tests/unit/text_encoding_test.ts b/cli/tests/unit/text_encoding_test.ts deleted file mode 100644 index 719e5907e..000000000 --- a/cli/tests/unit/text_encoding_test.ts +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertStrictEquals, - assertThrows, -} from "./test_util.ts"; - -Deno.test(function btoaSuccess() { - const text = "hello world"; - const encoded = btoa(text); - assertEquals(encoded, "aGVsbG8gd29ybGQ="); -}); - -Deno.test(function atobSuccess() { - const encoded = "aGVsbG8gd29ybGQ="; - const decoded = atob(encoded); - assertEquals(decoded, "hello world"); -}); - -Deno.test(function atobWithAsciiWhitespace() { - const encodedList = [ - " aGVsbG8gd29ybGQ=", - " aGVsbG8gd29ybGQ=", - "aGVsbG8gd29ybGQ= ", - "aGVsbG8gd29ybGQ=\n", - "aGVsbG\t8gd29ybGQ=", - `aGVsbG\t8g - d29ybGQ=`, - ]; - - for (const encoded of encodedList) { - const decoded = atob(encoded); - assertEquals(decoded, "hello world"); - } -}); - -Deno.test(function atobThrows() { - let threw = false; - try { - atob("aGVsbG8gd29ybGQ=="); - } catch (_e) { - threw = true; - } - assert(threw); -}); - -Deno.test(function atobThrows2() { - let threw = false; - try { - atob("aGVsbG8gd29ybGQ==="); - } catch (_e) { - threw = true; - } - assert(threw); -}); - -Deno.test(function atobThrows3() { - let threw = false; - try { - atob("foobar!!"); - } catch (e) { - if ( - e instanceof DOMException && - e.toString().startsWith("InvalidCharacterError:") - ) { - threw = true; - } - } - assert(threw); -}); - -Deno.test(function btoaFailed() { - const text = "你好"; - assertThrows(() => { - btoa(text); - }, DOMException); -}); - -Deno.test(function textDecoder2() { - // deno-fmt-ignore - const fixture = new Uint8Array([ - 0xf0, 0x9d, 0x93, 0xbd, - 0xf0, 0x9d, 0x93, 0xae, - 0xf0, 0x9d, 0x94, 0x81, - 0xf0, 0x9d, 0x93, 0xbd - ]); - const decoder = new TextDecoder(); - assertEquals(decoder.decode(fixture), "𝓽𝓮𝔁𝓽"); -}); - -// ignoreBOM is tested through WPT - -Deno.test(function textDecoderASCII() { - const fixture = new Uint8Array([0x89, 0x95, 0x9f, 0xbf]); - const decoder = new TextDecoder("ascii"); - assertEquals(decoder.decode(fixture), "‰•Ÿ¿"); -}); - -Deno.test(function textDecoderErrorEncoding() { - let didThrow = false; - try { - new TextDecoder("Foo"); - } catch (e) { - didThrow = true; - assert(e instanceof Error); - assertEquals(e.message, "The encoding label provided ('Foo') is invalid."); - } - assert(didThrow); -}); - -Deno.test(function textEncoder() { - const fixture = "𝓽𝓮𝔁𝓽"; - const encoder = new TextEncoder(); - // deno-fmt-ignore - assertEquals(Array.from(encoder.encode(fixture)), [ - 0xf0, 0x9d, 0x93, 0xbd, - 0xf0, 0x9d, 0x93, 0xae, - 0xf0, 0x9d, 0x94, 0x81, - 0xf0, 0x9d, 0x93, 0xbd - ]); -}); - -Deno.test(function textEncodeInto() { - const fixture = "text"; - const encoder = new TextEncoder(); - const bytes = new Uint8Array(5); - const result = encoder.encodeInto(fixture, bytes); - assertEquals(result.read, 4); - assertEquals(result.written, 4); - // deno-fmt-ignore - assertEquals(Array.from(bytes), [ - 0x74, 0x65, 0x78, 0x74, 0x00, - ]); -}); - -Deno.test(function textEncodeInto2() { - const fixture = "𝓽𝓮𝔁𝓽"; - const encoder = new TextEncoder(); - const bytes = new Uint8Array(17); - const result = encoder.encodeInto(fixture, bytes); - assertEquals(result.read, 8); - assertEquals(result.written, 16); - // deno-fmt-ignore - assertEquals(Array.from(bytes), [ - 0xf0, 0x9d, 0x93, 0xbd, - 0xf0, 0x9d, 0x93, 0xae, - 0xf0, 0x9d, 0x94, 0x81, - 0xf0, 0x9d, 0x93, 0xbd, 0x00, - ]); -}); - -Deno.test(function textEncodeInto3() { - const fixture = "𝓽𝓮𝔁𝓽"; - const encoder = new TextEncoder(); - const bytes = new Uint8Array(5); - const result = encoder.encodeInto(fixture, bytes); - assertEquals(result.read, 2); - assertEquals(result.written, 4); - // deno-fmt-ignore - assertEquals(Array.from(bytes), [ - 0xf0, 0x9d, 0x93, 0xbd, 0x00, - ]); -}); - -Deno.test(function loneSurrogateEncodeInto() { - const fixture = "lone𝄞\ud888surrogate"; - const encoder = new TextEncoder(); - const bytes = new Uint8Array(20); - const result = encoder.encodeInto(fixture, bytes); - assertEquals(result.read, 16); - assertEquals(result.written, 20); - // deno-fmt-ignore - assertEquals(Array.from(bytes), [ - 0x6c, 0x6f, 0x6e, 0x65, - 0xf0, 0x9d, 0x84, 0x9e, - 0xef, 0xbf, 0xbd, 0x73, - 0x75, 0x72, 0x72, 0x6f, - 0x67, 0x61, 0x74, 0x65 - ]); -}); - -Deno.test(function loneSurrogateEncodeInto2() { - const fixture = "\ud800"; - const encoder = new TextEncoder(); - const bytes = new Uint8Array(3); - const result = encoder.encodeInto(fixture, bytes); - assertEquals(result.read, 1); - assertEquals(result.written, 3); - // deno-fmt-ignore - assertEquals(Array.from(bytes), [ - 0xef, 0xbf, 0xbd - ]); -}); - -Deno.test(function loneSurrogateEncodeInto3() { - const fixture = "\udc00"; - const encoder = new TextEncoder(); - const bytes = new Uint8Array(3); - const result = encoder.encodeInto(fixture, bytes); - assertEquals(result.read, 1); - assertEquals(result.written, 3); - // deno-fmt-ignore - assertEquals(Array.from(bytes), [ - 0xef, 0xbf, 0xbd - ]); -}); - -Deno.test(function swappedSurrogatePairEncodeInto4() { - const fixture = "\udc00\ud800"; - const encoder = new TextEncoder(); - const bytes = new Uint8Array(8); - const result = encoder.encodeInto(fixture, bytes); - assertEquals(result.read, 2); - assertEquals(result.written, 6); - // deno-fmt-ignore - assertEquals(Array.from(bytes), [ - 0xef, 0xbf, 0xbd, 0xef, 0xbf, 0xbd, 0x00, 0x00 - ]); -}); - -Deno.test(function textDecoderSharedUint8Array() { - const ab = new SharedArrayBuffer(6); - const dataView = new DataView(ab); - const charCodeA = "A".charCodeAt(0); - for (let i = 0; i < ab.byteLength; i++) { - dataView.setUint8(i, charCodeA + i); - } - const ui8 = new Uint8Array(ab); - const decoder = new TextDecoder(); - const actual = decoder.decode(ui8); - assertEquals(actual, "ABCDEF"); -}); - -Deno.test(function textDecoderSharedInt32Array() { - const ab = new SharedArrayBuffer(8); - const dataView = new DataView(ab); - const charCodeA = "A".charCodeAt(0); - for (let i = 0; i < ab.byteLength; i++) { - dataView.setUint8(i, charCodeA + i); - } - const i32 = new Int32Array(ab); - const decoder = new TextDecoder(); - const actual = decoder.decode(i32); - assertEquals(actual, "ABCDEFGH"); -}); - -Deno.test(function toStringShouldBeWebCompatibility() { - const encoder = new TextEncoder(); - assertEquals(encoder.toString(), "[object TextEncoder]"); - - const decoder = new TextDecoder(); - assertEquals(decoder.toString(), "[object TextDecoder]"); -}); - -Deno.test(function textEncoderShouldCoerceToString() { - const encoder = new TextEncoder(); - const fixtureText = "text"; - const fixture = { - toString() { - return fixtureText; - }, - }; - - const bytes = encoder.encode(fixture as unknown as string); - const decoder = new TextDecoder(); - const decoded = decoder.decode(bytes); - assertEquals(decoded, fixtureText); -}); - -Deno.test(function binaryEncode() { - // @ts-ignore: Deno[Deno.internal].core allowed - const core = Deno[Deno.internal].core; - function asBinaryString(bytes: Uint8Array): string { - return Array.from(bytes).map( - (v: number) => String.fromCodePoint(v), - ).join(""); - } - - function decodeBinary(binaryString: string) { - const chars: string[] = Array.from(binaryString); - return chars.map((v: string): number | undefined => v.codePointAt(0)); - } - const inputs = [ - "σ😀", - "Кириллица is Cyrillic", - "𝓽𝓮𝔁𝓽", - "lone𝄞\ud888surrogate", - "\udc00\ud800", - "\ud800", - ]; - for (const input of inputs) { - const bytes = new TextEncoder().encode(input); - const binaryString = core.encodeBinaryString(bytes); - assertEquals( - binaryString, - asBinaryString(bytes), - ); - - assertEquals(Array.from(bytes), decodeBinary(binaryString)); - } -}); - -Deno.test( - { permissions: { read: true } }, - async function textDecoderStreamCleansUpOnCancel() { - let cancelled = false; - const readable = new ReadableStream({ - start: (controller) => { - controller.enqueue(new Uint8Array(12)); - }, - cancel: () => { - cancelled = true; - }, - }).pipeThrough(new TextDecoderStream()); - const chunks = []; - for await (const chunk of readable) { - chunks.push(chunk); - // breaking out of the loop prevents normal shutdown at end of async iterator values and triggers the cancel method of the stream instead - break; - } - assertEquals(chunks.length, 1); - assertEquals(chunks[0].length, 12); - assertStrictEquals(cancelled, true); - }, -); diff --git a/cli/tests/unit/timers_test.ts b/cli/tests/unit/timers_test.ts deleted file mode 100644 index 17b137231..000000000 --- a/cli/tests/unit/timers_test.ts +++ /dev/null @@ -1,763 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertNotEquals, - delay, - execCode, - unreachable, -} from "./test_util.ts"; - -Deno.test(async function functionParameterBindingSuccess() { - const { promise, resolve } = Promise.withResolvers<void>(); - let count = 0; - - const nullProto = (newCount: number) => { - count = newCount; - resolve(); - }; - - Reflect.setPrototypeOf(nullProto, null); - - setTimeout(nullProto, 500, 1); - await promise; - // count should be reassigned - assertEquals(count, 1); -}); - -Deno.test(async function stringifyAndEvalNonFunctions() { - // eval can only access global scope - const global = globalThis as unknown as { - globalPromise: ReturnType<typeof Promise.withResolvers<void>>; - globalCount: number; - }; - - global.globalPromise = Promise.withResolvers<void>(); - global.globalCount = 0; - - const notAFunction = - "globalThis.globalCount++; globalThis.globalPromise.resolve();" as unknown as () => - void; - - setTimeout(notAFunction, 500); - - await global.globalPromise.promise; - - // count should be incremented - assertEquals(global.globalCount, 1); - - Reflect.deleteProperty(global, "globalPromise"); - Reflect.deleteProperty(global, "globalCount"); -}); - -Deno.test(async function timeoutSuccess() { - const { promise, resolve } = Promise.withResolvers<void>(); - let count = 0; - setTimeout(() => { - count++; - resolve(); - }, 500); - await promise; - // count should increment - assertEquals(count, 1); -}); - -Deno.test(async function timeoutEvalNoScopeLeak() { - // eval can only access global scope - const global = globalThis as unknown as { - globalPromise: ReturnType<typeof Promise.withResolvers<Error>>; - }; - global.globalPromise = Promise.withResolvers(); - setTimeout( - ` - try { - console.log(core); - globalThis.globalPromise.reject(new Error("Didn't throw.")); - } catch (error) { - globalThis.globalPromise.resolve(error); - }` as unknown as () => void, - 0, - ); - const error = await global.globalPromise.promise; - assertEquals(error.name, "ReferenceError"); - Reflect.deleteProperty(global, "globalPromise"); -}); - -Deno.test(async function evalPrimordial() { - const global = globalThis as unknown as { - globalPromise: ReturnType<typeof Promise.withResolvers<void>>; - }; - global.globalPromise = Promise.withResolvers<void>(); - const originalEval = globalThis.eval; - let wasCalled = false; - globalThis.eval = (argument) => { - wasCalled = true; - return originalEval(argument); - }; - setTimeout( - "globalThis.globalPromise.resolve();" as unknown as () => void, - 0, - ); - await global.globalPromise.promise; - assert(!wasCalled); - Reflect.deleteProperty(global, "globalPromise"); - globalThis.eval = originalEval; -}); - -Deno.test(async function timeoutArgs() { - const { promise, resolve } = Promise.withResolvers<void>(); - const arg = 1; - let capturedArgs: unknown[] = []; - setTimeout( - function () { - capturedArgs = [...arguments]; - resolve(); - }, - 10, - arg, - arg.toString(), - [arg], - ); - await promise; - assertEquals(capturedArgs, [ - arg, - arg.toString(), - [arg], - ]); -}); - -Deno.test(async function timeoutCancelSuccess() { - let count = 0; - const id = setTimeout(() => { - count++; - }, 1); - // Cancelled, count should not increment - clearTimeout(id); - await delay(600); - assertEquals(count, 0); -}); - -Deno.test(async function timeoutCancelMultiple() { - function uncalled(): never { - throw new Error("This function should not be called."); - } - - // Set timers and cancel them in the same order. - const t1 = setTimeout(uncalled, 10); - const t2 = setTimeout(uncalled, 10); - const t3 = setTimeout(uncalled, 10); - clearTimeout(t1); - clearTimeout(t2); - clearTimeout(t3); - - // Set timers and cancel them in reverse order. - const t4 = setTimeout(uncalled, 20); - const t5 = setTimeout(uncalled, 20); - const t6 = setTimeout(uncalled, 20); - clearTimeout(t6); - clearTimeout(t5); - clearTimeout(t4); - - // Sleep until we're certain that the cancelled timers aren't gonna fire. - await delay(50); -}); - -Deno.test(async function timeoutCancelInvalidSilentFail() { - // Expect no panic - const { promise, resolve } = Promise.withResolvers<void>(); - let count = 0; - const id = setTimeout(() => { - count++; - // Should have no effect - clearTimeout(id); - resolve(); - }, 500); - await promise; - assertEquals(count, 1); - - // Should silently fail (no panic) - clearTimeout(2147483647); -}); - -Deno.test(async function intervalSuccess() { - const { promise, resolve } = Promise.withResolvers<void>(); - let count = 0; - const id = setInterval(() => { - count++; - clearInterval(id); - resolve(); - }, 100); - await promise; - // Clear interval - clearInterval(id); - // count should increment twice - assertEquals(count, 1); - // Similar false async leaking alarm. - // Force next round of polling. - await delay(0); -}); - -Deno.test(async function intervalCancelSuccess() { - let count = 0; - const id = setInterval(() => { - count++; - }, 1); - clearInterval(id); - await delay(500); - assertEquals(count, 0); -}); - -Deno.test(async function intervalOrdering() { - const timers: number[] = []; - let timeouts = 0; - function onTimeout() { - ++timeouts; - for (let i = 1; i < timers.length; i++) { - clearTimeout(timers[i]); - } - } - for (let i = 0; i < 10; i++) { - timers[i] = setTimeout(onTimeout, 1); - } - await delay(500); - assertEquals(timeouts, 1); -}); - -Deno.test(function intervalCancelInvalidSilentFail() { - // Should silently fail (no panic) - clearInterval(2147483647); -}); - -Deno.test(async function callbackTakesLongerThanInterval() { - const { promise, resolve } = Promise.withResolvers<void>(); - - let timeEndOfFirstCallback: number | undefined; - const interval = setInterval(() => { - if (timeEndOfFirstCallback === undefined) { - // First callback - Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 300); - timeEndOfFirstCallback = Date.now(); - } else { - // Second callback - assert(Date.now() - 100 >= timeEndOfFirstCallback); - clearInterval(interval); - resolve(); - } - }, 100); - - await promise; -}); - -// https://github.com/denoland/deno/issues/11398 -Deno.test(async function clearTimeoutAfterNextTimerIsDue1() { - const { promise, resolve } = Promise.withResolvers<void>(); - - setTimeout(() => { - resolve(); - }, 300); - - const interval = setInterval(() => { - Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 400); - // Both the interval and the timeout's due times are now in the past. - clearInterval(interval); - }, 100); - - await promise; -}); - -// https://github.com/denoland/deno/issues/11398 -Deno.test(async function clearTimeoutAfterNextTimerIsDue2() { - const { promise, resolve } = Promise.withResolvers<void>(); - - const timeout1 = setTimeout(unreachable, 100); - - setTimeout(() => { - resolve(); - }, 200); - - Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 300); - // Both of the timeouts' due times are now in the past. - clearTimeout(timeout1); - - await promise; -}); - -Deno.test(async function fireCallbackImmediatelyWhenDelayOverMaxValue() { - let count = 0; - setTimeout(() => { - count++; - }, 2 ** 31); - await delay(1); - assertEquals(count, 1); -}); - -Deno.test(async function timeoutCallbackThis() { - const { promise, resolve } = Promise.withResolvers<void>(); - let capturedThis: unknown; - const obj = { - foo() { - capturedThis = this; - resolve(); - }, - }; - setTimeout(obj.foo, 1); - await promise; - assertEquals(capturedThis, window); -}); - -Deno.test(async function timeoutBindThis() { - const thisCheckPassed = [null, undefined, window, globalThis]; - - const thisCheckFailed = [ - 0, - "", - true, - false, - {}, - [], - "foo", - () => {}, - Object.prototype, - ]; - - for (const thisArg of thisCheckPassed) { - const { promise, resolve } = Promise.withResolvers<void>(); - let hasThrown = 0; - try { - setTimeout.call(thisArg, () => resolve(), 1); - hasThrown = 1; - } catch (err) { - if (err instanceof TypeError) { - hasThrown = 2; - } else { - hasThrown = 3; - } - } - await promise; - assertEquals(hasThrown, 1); - } - - for (const thisArg of thisCheckFailed) { - let hasThrown = 0; - try { - setTimeout.call(thisArg, () => {}, 1); - hasThrown = 1; - } catch (err) { - if (err instanceof TypeError) { - hasThrown = 2; - } else { - hasThrown = 3; - } - } - assertEquals(hasThrown, 2); - } -}); - -Deno.test(function clearTimeoutShouldConvertToNumber() { - let called = false; - const obj = { - valueOf(): number { - called = true; - return 1; - }, - }; - clearTimeout((obj as unknown) as number); - assert(called); -}); - -Deno.test(function setTimeoutShouldThrowWithBigint() { - let hasThrown = 0; - try { - setTimeout(() => {}, (1n as unknown) as number); - hasThrown = 1; - } catch (err) { - if (err instanceof TypeError) { - hasThrown = 2; - } else { - hasThrown = 3; - } - } - assertEquals(hasThrown, 2); -}); - -Deno.test(function clearTimeoutShouldThrowWithBigint() { - let hasThrown = 0; - try { - clearTimeout((1n as unknown) as number); - hasThrown = 1; - } catch (err) { - if (err instanceof TypeError) { - hasThrown = 2; - } else { - hasThrown = 3; - } - } - assertEquals(hasThrown, 2); -}); - -Deno.test(function testFunctionName() { - assertEquals(clearTimeout.name, "clearTimeout"); - assertEquals(clearInterval.name, "clearInterval"); -}); - -Deno.test(function testFunctionParamsLength() { - assertEquals(setTimeout.length, 1); - assertEquals(setInterval.length, 1); - assertEquals(clearTimeout.length, 0); - assertEquals(clearInterval.length, 0); -}); - -Deno.test(function clearTimeoutAndClearIntervalNotBeEquals() { - assertNotEquals(clearTimeout, clearInterval); -}); - -Deno.test(async function timerOrdering() { - const array: number[] = []; - const { promise: donePromise, resolve } = Promise.withResolvers<void>(); - - function push(n: number) { - array.push(n); - if (array.length === 6) { - resolve(); - } - } - - setTimeout(() => { - push(1); - setTimeout(() => push(4)); - }, 0); - setTimeout(() => { - push(2); - setTimeout(() => push(5)); - }, 0); - setTimeout(() => { - push(3); - setTimeout(() => push(6)); - }, 0); - - await donePromise; - - assertEquals(array, [1, 2, 3, 4, 5, 6]); -}); - -Deno.test(async function timerBasicMicrotaskOrdering() { - let s = ""; - let count = 0; - const { promise, resolve } = Promise.withResolvers<void>(); - setTimeout(() => { - Promise.resolve().then(() => { - count++; - s += "de"; - if (count === 2) { - resolve(); - } - }); - }); - setTimeout(() => { - count++; - s += "no"; - if (count === 2) { - resolve(); - } - }); - await promise; - assertEquals(s, "deno"); -}); - -Deno.test(async function timerNestedMicrotaskOrdering() { - let s = ""; - const { promise, resolve } = Promise.withResolvers<void>(); - s += "0"; - setTimeout(() => { - s += "4"; - setTimeout(() => (s += "A")); - Promise.resolve() - .then(() => { - setTimeout(() => { - s += "B"; - resolve(); - }); - }) - .then(() => { - s += "5"; - }); - }); - setTimeout(() => (s += "6")); - Promise.resolve().then(() => (s += "2")); - Promise.resolve().then(() => - setTimeout(() => { - s += "7"; - Promise.resolve() - .then(() => (s += "8")) - .then(() => { - s += "9"; - }); - }) - ); - Promise.resolve().then(() => Promise.resolve().then(() => (s += "3"))); - s += "1"; - await promise; - assertEquals(s, "0123456789AB"); -}); - -Deno.test(function testQueueMicrotask() { - assertEquals(typeof queueMicrotask, "function"); -}); - -Deno.test(async function timerIgnoresDateOverride() { - const OriginalDate = Date; - const { promise, resolve, reject } = Promise.withResolvers<void>(); - let hasThrown = 0; - try { - const overrideCalled: () => number = () => { - reject("global Date override used over original Date object"); - return 0; - }; - const DateOverride = () => { - overrideCalled(); - }; - globalThis.Date = DateOverride as DateConstructor; - globalThis.Date.now = overrideCalled; - globalThis.Date.UTC = overrideCalled; - globalThis.Date.parse = overrideCalled; - queueMicrotask(() => { - resolve(); - }); - await promise; - hasThrown = 1; - } catch (err) { - if (typeof err === "string") { - assertEquals(err, "global Date override used over original Date object"); - hasThrown = 2; - } else if (err instanceof TypeError) { - hasThrown = 3; - } else { - hasThrown = 4; - } - } finally { - globalThis.Date = OriginalDate; - } - assertEquals(hasThrown, 1); -}); - -Deno.test({ - name: "unrefTimer", - permissions: { run: true, read: true }, - fn: async () => { - const [statusCode, output] = await execCode(` - const timer = setTimeout(() => console.log("1"), 1); - Deno.unrefTimer(timer); - `); - assertEquals(statusCode, 0); - assertEquals(output, ""); - }, -}); - -Deno.test({ - name: "unrefTimer - mix ref and unref 1", - permissions: { run: true, read: true }, - fn: async () => { - const [statusCode, output] = await execCode(` - const timer1 = setTimeout(() => console.log("1"), 200); - const timer2 = setTimeout(() => console.log("2"), 400); - const timer3 = setTimeout(() => console.log("3"), 600); - Deno.unrefTimer(timer3); - `); - assertEquals(statusCode, 0); - assertEquals(output, "1\n2\n"); - }, -}); - -Deno.test({ - name: "unrefTimer - mix ref and unref 2", - permissions: { run: true, read: true }, - fn: async () => { - const [statusCode, output] = await execCode(` - const timer1 = setTimeout(() => console.log("1"), 200); - const timer2 = setTimeout(() => console.log("2"), 400); - const timer3 = setTimeout(() => console.log("3"), 600); - Deno.unrefTimer(timer1); - Deno.unrefTimer(timer2); - `); - assertEquals(statusCode, 0); - assertEquals(output, "1\n2\n3\n"); - }, -}); - -Deno.test({ - name: "unrefTimer - unref interval", - permissions: { run: true, read: true }, - fn: async () => { - const [statusCode, output] = await execCode(` - let i = 0; - const timer1 = setInterval(() => { - console.log("1"); - i++; - if (i === 5) { - Deno.unrefTimer(timer1); - } - }, 10); - `); - assertEquals(statusCode, 0); - assertEquals(output, "1\n1\n1\n1\n1\n"); - }, -}); - -Deno.test({ - name: "unrefTimer - unref then ref 1", - permissions: { run: true, read: true }, - fn: async () => { - const [statusCode, output] = await execCode(` - const timer1 = setTimeout(() => console.log("1"), 10); - Deno.unrefTimer(timer1); - Deno.refTimer(timer1); - `); - assertEquals(statusCode, 0); - assertEquals(output, "1\n"); - }, -}); - -Deno.test({ - name: "unrefTimer - unref then ref", - permissions: { run: true, read: true }, - fn: async () => { - const [statusCode, output] = await execCode(` - const timer1 = setTimeout(() => { - console.log("1"); - Deno.refTimer(timer2); - }, 10); - const timer2 = setTimeout(() => console.log("2"), 20); - Deno.unrefTimer(timer2); - `); - assertEquals(statusCode, 0); - assertEquals(output, "1\n2\n"); - }, -}); - -Deno.test({ - name: "unrefTimer - invalid calls do nothing", - fn: () => { - Deno.unrefTimer(NaN); - Deno.refTimer(NaN); - }, -}); - -Deno.test({ - name: "AbortSignal.timeout() with no listeners", - permissions: { run: true, read: true }, - fn: async () => { - const [statusCode, output] = await execCode(` - const signal = AbortSignal.timeout(2000); - - // This unref timer expires before the signal, and if it does expire, then - // it means the signal has kept the event loop alive. - const timer = setTimeout(() => console.log("Unexpected!"), 1500); - Deno.unrefTimer(timer); - `); - assertEquals(statusCode, 0); - assertEquals(output, ""); - }, -}); - -Deno.test({ - name: "AbortSignal.timeout() with listeners", - permissions: { run: true, read: true }, - fn: async () => { - const [statusCode, output] = await execCode(` - const signal = AbortSignal.timeout(1000); - signal.addEventListener("abort", () => console.log("Event fired!")); - `); - assertEquals(statusCode, 0); - assertEquals(output, "Event fired!\n"); - }, -}); - -Deno.test({ - name: "AbortSignal.timeout() with removed listeners", - permissions: { run: true, read: true }, - fn: async () => { - const [statusCode, output] = await execCode(` - const signal = AbortSignal.timeout(2000); - - const callback = () => console.log("Unexpected: Event fired"); - signal.addEventListener("abort", callback); - - setTimeout(() => { - console.log("Removing the listener."); - signal.removeEventListener("abort", callback); - }, 500); - - Deno.unrefTimer( - setTimeout(() => console.log("Unexpected: Unref timer"), 1500) - ); - `); - assertEquals(statusCode, 0); - assertEquals(output, "Removing the listener.\n"); - }, -}); - -Deno.test({ - name: "AbortSignal.timeout() with listener for a non-abort event", - permissions: { run: true, read: true }, - fn: async () => { - const [statusCode, output] = await execCode(` - const signal = AbortSignal.timeout(2000); - - signal.addEventListener("someOtherEvent", () => { - console.log("Unexpected: someOtherEvent called"); - }); - - Deno.unrefTimer( - setTimeout(() => console.log("Unexpected: Unref timer"), 1500) - ); - `); - assertEquals(statusCode, 0); - assertEquals(output, ""); - }, -}); - -// Regression test for https://github.com/denoland/deno/issues/19866 -Deno.test({ - name: "regression for #19866", - fn: async () => { - const timeoutsFired = []; - - // deno-lint-ignore require-await - async function start(n: number) { - let i = 0; - const intervalId = setInterval(() => { - i++; - if (i > 2) { - clearInterval(intervalId!); - } - timeoutsFired.push(n); - }, 20); - } - - for (let n = 0; n < 100; n++) { - start(n); - } - - // 3s should be plenty of time for all the intervals to fire - // but it might still be flaky on CI. - await new Promise((resolve) => setTimeout(resolve, 3000)); - assertEquals(timeoutsFired.length, 300); - }, -}); - -// Regression test for https://github.com/denoland/deno/issues/20367 -Deno.test({ - name: "regression for #20367", - fn: async () => { - const { promise, resolve } = Promise.withResolvers<number>(); - const start = performance.now(); - setTimeout(() => { - const end = performance.now(); - resolve(end - start); - }, 1000); - clearTimeout(setTimeout(() => {}, 1000)); - - const result = await promise; - assert(result >= 1000); - }, -}); diff --git a/cli/tests/unit/tls_test.ts b/cli/tests/unit/tls_test.ts deleted file mode 100644 index bbf48cf8f..000000000 --- a/cli/tests/unit/tls_test.ts +++ /dev/null @@ -1,1546 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertNotEquals, - assertRejects, - assertStrictEquals, - assertThrows, -} from "./test_util.ts"; -import { BufReader, BufWriter } from "@test_util/std/io/mod.ts"; -import { readAll } from "@test_util/std/streams/read_all.ts"; -import { writeAll } from "@test_util/std/streams/write_all.ts"; -import { TextProtoReader } from "../testdata/run/textproto.ts"; - -const encoder = new TextEncoder(); -const decoder = new TextDecoder(); -const cert = await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"); -const key = await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"); -const caCerts = [await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem")]; - -async function sleep(msec: number) { - await new Promise((res, _rej) => setTimeout(res, msec)); -} - -function unreachable(): never { - throw new Error("Unreachable code reached"); -} - -Deno.test({ permissions: { net: false } }, async function connectTLSNoPerm() { - await assertRejects(async () => { - await Deno.connectTls({ hostname: "deno.land", port: 443 }); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { permissions: { read: true, net: true } }, - async function connectTLSInvalidHost() { - await assertRejects(async () => { - await Deno.connectTls({ hostname: "256.0.0.0", port: 3567 }); - }, TypeError); - }, -); - -Deno.test( - { permissions: { net: true, read: false } }, - async function connectTLSCertFileNoReadPerm() { - await assertRejects(async () => { - await Deno.connectTls({ - hostname: "deno.land", - port: 443, - certFile: "cli/tests/testdata/tls/RootCA.crt", - }); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - function listenTLSNonExistentCertKeyFiles() { - const options = { - hostname: "localhost", - port: 3500, - certFile: "cli/tests/testdata/tls/localhost.crt", - keyFile: "cli/tests/testdata/tls/localhost.key", - }; - - assertThrows(() => { - Deno.listenTls({ - ...options, - certFile: "./non/existent/file", - }); - }, Deno.errors.NotFound); - - assertThrows(() => { - Deno.listenTls({ - ...options, - keyFile: "./non/existent/file", - }); - }, Deno.errors.NotFound); - }, -); - -Deno.test( - { permissions: { net: true, read: false } }, - function listenTLSNoReadPerm() { - assertThrows(() => { - Deno.listenTls({ - hostname: "localhost", - port: 3500, - certFile: "cli/tests/testdata/tls/localhost.crt", - keyFile: "cli/tests/testdata/tls/localhost.key", - }); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { - permissions: { read: true, write: true, net: true }, - }, - function listenTLSEmptyKeyFile() { - const options = { - hostname: "localhost", - port: 3500, - certFile: "cli/tests/testdata/tls/localhost.crt", - keyFile: "cli/tests/testdata/tls/localhost.key", - }; - - const testDir = Deno.makeTempDirSync(); - const keyFilename = testDir + "/key.pem"; - Deno.writeFileSync(keyFilename, new Uint8Array([]), { - mode: 0o666, - }); - - assertThrows(() => { - Deno.listenTls({ - ...options, - keyFile: keyFilename, - }); - }, Error); - }, -); - -Deno.test( - { permissions: { read: true, write: true, net: true } }, - function listenTLSEmptyCertFile() { - const options = { - hostname: "localhost", - port: 3500, - certFile: "cli/tests/testdata/tls/localhost.crt", - keyFile: "cli/tests/testdata/tls/localhost.key", - }; - - const testDir = Deno.makeTempDirSync(); - const certFilename = testDir + "/cert.crt"; - Deno.writeFileSync(certFilename, new Uint8Array([]), { - mode: 0o666, - }); - - assertThrows(() => { - Deno.listenTls({ - ...options, - certFile: certFilename, - }); - }, Error); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function startTlsWithoutExclusiveAccessToTcpConn() { - const hostname = "localhost"; - const port = getPort(); - - const tcpListener = Deno.listen({ hostname, port }); - const [serverConn, clientConn] = await Promise.all([ - tcpListener.accept(), - Deno.connect({ hostname, port }), - ]); - - const buf = new Uint8Array(128); - const readPromise = clientConn.read(buf); - // `clientConn` is being used by a pending promise (`readPromise`) so - // `Deno.startTls` cannot consume the connection. - await assertRejects( - () => Deno.startTls(clientConn, { hostname }), - Deno.errors.BadResource, - ); - - serverConn.close(); - tcpListener.close(); - await readPromise; - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function dialAndListenTLS() { - const { promise, resolve } = Promise.withResolvers<void>(); - const hostname = "localhost"; - const port = 3500; - - const listener = Deno.listenTls({ - hostname, - port, - cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"), - key: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"), - }); - - const response = encoder.encode( - "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n", - ); - - listener.accept().then( - async (conn) => { - assert(conn.remoteAddr != null); - assert(conn.localAddr != null); - await conn.write(response); - // TODO(bartlomieju): this might be a bug - setTimeout(() => { - conn.close(); - resolve(); - }, 0); - }, - ); - - const conn = await Deno.connectTls({ hostname, port, caCerts }); - assert(conn.rid > 0); - const w = new BufWriter(conn); - const r = new BufReader(conn); - const body = `GET / HTTP/1.1\r\nHost: ${hostname}:${port}\r\n\r\n`; - const writeResult = await w.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await w.flush(); - const tpr = new TextProtoReader(r); - const statusLine = await tpr.readLine(); - assert(statusLine !== null, `line must be read: ${String(statusLine)}`); - const m = statusLine.match(/^(.+?) (.+?) (.+?)$/); - assert(m !== null, "must be matched"); - const [_, proto, status, ok] = m; - assertEquals(proto, "HTTP/1.1"); - assertEquals(status, "200"); - assertEquals(ok, "OK"); - const headers = await tpr.readMimeHeader(); - assert(headers !== null); - const contentLength = parseInt(headers.get("content-length")!); - const bodyBuf = new Uint8Array(contentLength); - await r.readFull(bodyBuf); - assertEquals(decoder.decode(bodyBuf), "Hello World\n"); - conn.close(); - listener.close(); - await promise; - }, -); -Deno.test( - { permissions: { read: false, net: true } }, - async function listenTlsWithCertAndKey() { - const { promise, resolve } = Promise.withResolvers<void>(); - const hostname = "localhost"; - const port = 3500; - - const listener = Deno.listenTls({ hostname, port, cert, key }); - - const response = encoder.encode( - "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n", - ); - - listener.accept().then( - async (conn) => { - assert(conn.remoteAddr != null); - assert(conn.localAddr != null); - await conn.write(response); - setTimeout(() => { - conn.close(); - resolve(); - }, 0); - }, - ); - - const conn = await Deno.connectTls({ hostname, port, caCerts }); - assert(conn.rid > 0); - const w = new BufWriter(conn); - const r = new BufReader(conn); - const body = `GET / HTTP/1.1\r\nHost: ${hostname}:${port}\r\n\r\n`; - const writeResult = await w.write(encoder.encode(body)); - assertEquals(body.length, writeResult); - await w.flush(); - const tpr = new TextProtoReader(r); - const statusLine = await tpr.readLine(); - assert(statusLine !== null, `line must be read: ${String(statusLine)}`); - const m = statusLine.match(/^(.+?) (.+?) (.+?)$/); - assert(m !== null, "must be matched"); - const [_, proto, status, ok] = m; - assertEquals(proto, "HTTP/1.1"); - assertEquals(status, "200"); - assertEquals(ok, "OK"); - const headers = await tpr.readMimeHeader(); - assert(headers !== null); - const contentLength = parseInt(headers.get("content-length")!); - const bodyBuf = new Uint8Array(contentLength); - await r.readFull(bodyBuf); - assertEquals(decoder.decode(bodyBuf), "Hello World\n"); - conn.close(); - listener.close(); - await promise; - }, -); - -let nextPort = 3501; -function getPort() { - return nextPort++; -} - -async function tlsPair(): Promise<[Deno.Conn, Deno.Conn]> { - const port = getPort(); - const listener = Deno.listenTls({ - hostname: "localhost", - port, - cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"), - key: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"), - }); - - const acceptPromise = listener.accept(); - const connectPromise = Deno.connectTls({ - hostname: "localhost", - port, - caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")], - }); - const endpoints = await Promise.all([acceptPromise, connectPromise]); - - listener.close(); - - return endpoints; -} - -async function tlsAlpn( - useStartTls: boolean, -): Promise<[Deno.TlsConn, Deno.TlsConn]> { - const port = getPort(); - const listener = Deno.listenTls({ - hostname: "localhost", - port, - cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"), - key: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"), - alpnProtocols: ["deno", "rocks"], - }); - - const acceptPromise = listener.accept(); - - const caCerts = [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")]; - const clientAlpnProtocols = ["rocks", "rises"]; - let endpoints: [Deno.TlsConn, Deno.TlsConn]; - - if (!useStartTls) { - const connectPromise = Deno.connectTls({ - hostname: "localhost", - port, - caCerts, - alpnProtocols: clientAlpnProtocols, - }); - endpoints = await Promise.all([acceptPromise, connectPromise]); - } else { - const client = await Deno.connect({ - hostname: "localhost", - port, - }); - const connectPromise = Deno.startTls(client, { - hostname: "localhost", - caCerts, - alpnProtocols: clientAlpnProtocols, - }); - endpoints = await Promise.all([acceptPromise, connectPromise]); - } - - listener.close(); - return endpoints; -} - -async function sendThenCloseWriteThenReceive( - conn: Deno.Conn, - chunkCount: number, - chunkSize: number, -) { - const byteCount = chunkCount * chunkSize; - const buf = new Uint8Array(chunkSize); // Note: buf is size of _chunk_. - let n: number; - - // Slowly send 42s. - buf.fill(42); - for (let remaining = byteCount; remaining > 0; remaining -= n) { - n = await conn.write(buf.subarray(0, remaining)); - assert(n >= 1); - await sleep(10); - } - - // Send EOF. - await conn.closeWrite(); - - // Receive 69s. - for (let remaining = byteCount; remaining > 0; remaining -= n) { - buf.fill(0); - n = await conn.read(buf) as number; - assert(n >= 1); - assertStrictEquals(buf[0], 69); - assertStrictEquals(buf[n - 1], 69); - } - - conn.close(); -} - -async function receiveThenSend( - conn: Deno.Conn, - chunkCount: number, - chunkSize: number, -) { - const byteCount = chunkCount * chunkSize; - const buf = new Uint8Array(byteCount); // Note: buf size equals `byteCount`. - let n: number; - - // Receive 42s. - for (let remaining = byteCount; remaining > 0; remaining -= n) { - buf.fill(0); - n = await conn.read(buf) as number; - assert(n >= 1); - assertStrictEquals(buf[0], 42); - assertStrictEquals(buf[n - 1], 42); - } - - // Slowly send 69s. - buf.fill(69); - for (let remaining = byteCount; remaining > 0; remaining -= n) { - n = await conn.write(buf.subarray(0, remaining)); - assert(n >= 1); - await sleep(10); - } - - conn.close(); -} - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsServerAlpnListenConnect() { - const [serverConn, clientConn] = await tlsAlpn(false); - const [serverHS, clientHS] = await Promise.all([ - serverConn.handshake(), - clientConn.handshake(), - ]); - assertStrictEquals(serverHS.alpnProtocol, "rocks"); - assertStrictEquals(clientHS.alpnProtocol, "rocks"); - - serverConn.close(); - clientConn.close(); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsServerAlpnListenStartTls() { - const [serverConn, clientConn] = await tlsAlpn(true); - const [serverHS, clientHS] = await Promise.all([ - serverConn.handshake(), - clientConn.handshake(), - ]); - assertStrictEquals(serverHS.alpnProtocol, "rocks"); - assertStrictEquals(clientHS.alpnProtocol, "rocks"); - - serverConn.close(); - clientConn.close(); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsServerStreamHalfCloseSendOneByte() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - sendThenCloseWriteThenReceive(serverConn, 1, 1), - receiveThenSend(clientConn, 1, 1), - ]); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsClientStreamHalfCloseSendOneByte() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - sendThenCloseWriteThenReceive(clientConn, 1, 1), - receiveThenSend(serverConn, 1, 1), - ]); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsServerStreamHalfCloseSendOneChunk() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - sendThenCloseWriteThenReceive(serverConn, 1, 1 << 20 /* 1 MB */), - receiveThenSend(clientConn, 1, 1 << 20 /* 1 MB */), - ]); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsClientStreamHalfCloseSendOneChunk() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - sendThenCloseWriteThenReceive(clientConn, 1, 1 << 20 /* 1 MB */), - receiveThenSend(serverConn, 1, 1 << 20 /* 1 MB */), - ]); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsServerStreamHalfCloseSendManyBytes() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - sendThenCloseWriteThenReceive(serverConn, 100, 1), - receiveThenSend(clientConn, 100, 1), - ]); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsClientStreamHalfCloseSendManyBytes() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - sendThenCloseWriteThenReceive(clientConn, 100, 1), - receiveThenSend(serverConn, 100, 1), - ]); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsServerStreamHalfCloseSendManyChunks() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - sendThenCloseWriteThenReceive(serverConn, 100, 1 << 16 /* 64 kB */), - receiveThenSend(clientConn, 100, 1 << 16 /* 64 kB */), - ]); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsClientStreamHalfCloseSendManyChunks() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - sendThenCloseWriteThenReceive(clientConn, 100, 1 << 16 /* 64 kB */), - receiveThenSend(serverConn, 100, 1 << 16 /* 64 kB */), - ]); - }, -); - -const largeAmount = 1 << 20 /* 1 MB */; - -async function sendAlotReceiveNothing(conn: Deno.Conn) { - // Start receive op. - const readBuf = new Uint8Array(1024); - const readPromise = conn.read(readBuf); - - const timeout = setTimeout(() => { - throw new Error("Failed to send buffer in a reasonable amount of time"); - }, 10_000); - - // Send 1 MB of data. - const writeBuf = new Uint8Array(largeAmount); - writeBuf.fill(42); - await writeAll(conn, writeBuf); - - clearTimeout(timeout); - - // Send EOF. - await conn.closeWrite(); - - // Close the connection. - conn.close(); - - // Read op should be canceled. - await assertRejects( - async () => await readPromise, - Deno.errors.Interrupted, - ); -} - -async function receiveAlotSendNothing(conn: Deno.Conn) { - const readBuf = new Uint8Array(1024); - let n: number | null; - let nread = 0; - - const timeout = setTimeout(() => { - throw new Error( - `Failed to read buffer in a reasonable amount of time (got ${nread}/${largeAmount})`, - ); - }, 10_000); - - // Receive 1 MB of data. - try { - for (; nread < largeAmount; nread += n!) { - n = await conn.read(readBuf); - assertStrictEquals(typeof n, "number"); - assert(n! > 0); - assertStrictEquals(readBuf[0], 42); - } - } catch (e) { - throw new Error( - `Got an error (${e.message}) after reading ${nread}/${largeAmount} bytes`, - { cause: e }, - ); - } - clearTimeout(timeout); - - // Close the connection, without sending anything at all. - conn.close(); -} - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsServerStreamCancelRead() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - sendAlotReceiveNothing(serverConn), - receiveAlotSendNothing(clientConn), - ]); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsClientStreamCancelRead() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - sendAlotReceiveNothing(clientConn), - receiveAlotSendNothing(serverConn), - ]); - }, -); - -async function sendReceiveEmptyBuf(conn: Deno.Conn) { - const byteBuf = new Uint8Array([1]); - const emptyBuf = new Uint8Array(0); - let n: number | null; - - n = await conn.write(emptyBuf); - assertStrictEquals(n, 0); - - n = await conn.read(emptyBuf); - assertStrictEquals(n, 0); - - n = await conn.write(byteBuf); - assertStrictEquals(n, 1); - - n = await conn.read(byteBuf); - assertStrictEquals(n, 1); - - await conn.closeWrite(); - - n = await conn.write(emptyBuf); - assertStrictEquals(n, 0); - - await assertRejects(async () => { - await conn.write(byteBuf); - }, Deno.errors.NotConnected); - - n = await conn.write(emptyBuf); - assertStrictEquals(n, 0); - - n = await conn.read(byteBuf); - assertStrictEquals(n, null); - - conn.close(); -} - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsStreamSendReceiveEmptyBuf() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - sendReceiveEmptyBuf(serverConn), - sendReceiveEmptyBuf(clientConn), - ]); - }, -); - -function immediateClose(conn: Deno.Conn) { - conn.close(); - return Promise.resolve(); -} - -async function closeWriteAndClose(conn: Deno.Conn) { - await conn.closeWrite(); - - if (await conn.read(new Uint8Array(1)) !== null) { - throw new Error("did not expect to receive data on TLS stream"); - } - - conn.close(); -} - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsServerStreamImmediateClose() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - immediateClose(serverConn), - closeWriteAndClose(clientConn), - ]); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsClientStreamImmediateClose() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - closeWriteAndClose(serverConn), - immediateClose(clientConn), - ]); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsClientAndServerStreamImmediateClose() { - const [serverConn, clientConn] = await tlsPair(); - await Promise.all([ - immediateClose(serverConn), - immediateClose(clientConn), - ]); - }, -); - -async function tlsWithTcpFailureTestImpl( - phase: "handshake" | "traffic", - cipherByteCount: number, - failureMode: "corruption" | "shutdown", - reverse: boolean, -) { - const tlsPort = getPort(); - const tlsListener = Deno.listenTls({ - hostname: "localhost", - port: tlsPort, - cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"), - key: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"), - }); - - const tcpPort = getPort(); - const tcpListener = Deno.listen({ hostname: "localhost", port: tcpPort }); - - const [tlsServerConn, tcpServerConn] = await Promise.all([ - tlsListener.accept(), - Deno.connect({ hostname: "localhost", port: tlsPort }), - ]); - - const [tcpClientConn, tlsClientConn] = await Promise.all([ - tcpListener.accept(), - Deno.connectTls({ - hostname: "localhost", - port: tcpPort, - caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")], - }), - ]); - - tlsListener.close(); - tcpListener.close(); - - const { - tlsConn1, - tlsConn2, - tcpConn1, - tcpConn2, - } = reverse - ? { - tlsConn1: tlsClientConn, - tlsConn2: tlsServerConn, - tcpConn1: tcpClientConn, - tcpConn2: tcpServerConn, - } - : { - tlsConn1: tlsServerConn, - tlsConn2: tlsClientConn, - tcpConn1: tcpServerConn, - tcpConn2: tcpClientConn, - }; - - const tcpForwardingInterruptDeferred1 = Promise.withResolvers<void>(); - const tcpForwardingPromise1 = forwardBytes( - tcpConn2, - tcpConn1, - cipherByteCount, - tcpForwardingInterruptDeferred1, - ); - - const tcpForwardingInterruptDeferred2 = Promise.withResolvers<void>(); - const tcpForwardingPromise2 = forwardBytes( - tcpConn1, - tcpConn2, - Infinity, - tcpForwardingInterruptDeferred2, - ); - - switch (phase) { - case "handshake": { - let expectedError; - switch (failureMode) { - case "corruption": - expectedError = Deno.errors.InvalidData; - break; - case "shutdown": - expectedError = Deno.errors.UnexpectedEof; - break; - default: - unreachable(); - } - - const tlsTrafficPromise1 = Promise.all([ - assertRejects( - () => sendBytes(tlsConn1, 0x01, 1), - expectedError, - ), - assertRejects( - () => receiveBytes(tlsConn1, 0x02, 1), - expectedError, - ), - ]); - - const tlsTrafficPromise2 = Promise.all([ - assertRejects( - () => sendBytes(tlsConn2, 0x02, 1), - Deno.errors.UnexpectedEof, - ), - assertRejects( - () => receiveBytes(tlsConn2, 0x01, 1), - Deno.errors.UnexpectedEof, - ), - ]); - - await tcpForwardingPromise1; - - switch (failureMode) { - case "corruption": - await sendBytes(tcpConn1, 0xff, 1 << 14 /* 16 kB */); - break; - case "shutdown": - await tcpConn1.closeWrite(); - break; - default: - unreachable(); - } - await tlsTrafficPromise1; - - tcpForwardingInterruptDeferred2.resolve(); - await tcpForwardingPromise2; - await tcpConn2.closeWrite(); - await tlsTrafficPromise2; - - break; - } - - case "traffic": { - await Promise.all([ - sendBytes(tlsConn2, 0x88, 8888), - receiveBytes(tlsConn1, 0x88, 8888), - sendBytes(tlsConn1, 0x99, 99999), - receiveBytes(tlsConn2, 0x99, 99999), - ]); - - tcpForwardingInterruptDeferred1.resolve(); - await tcpForwardingInterruptDeferred1.promise; - - switch (failureMode) { - case "corruption": - await sendBytes(tcpConn1, 0xff, 1 << 14 /* 16 kB */); - await assertRejects( - () => receiveEof(tlsConn1), - Deno.errors.InvalidData, - ); - tcpForwardingInterruptDeferred2.resolve(); - break; - case "shutdown": - await Promise.all([ - tcpConn1.closeWrite(), - await assertRejects( - () => receiveEof(tlsConn1), - Deno.errors.UnexpectedEof, - ), - await tlsConn1.closeWrite(), - await receiveEof(tlsConn2), - ]); - break; - default: - unreachable(); - } - - await tcpForwardingPromise2; - - break; - } - - default: - unreachable(); - } - - tlsServerConn.close(); - tlsClientConn.close(); - tcpServerConn.close(); - tcpClientConn.close(); - - async function sendBytes( - conn: Deno.Conn, - byte: number, - count: number, - ) { - let buf = new Uint8Array(1 << 12 /* 4 kB */); - buf.fill(byte); - - while (count > 0) { - buf = buf.subarray(0, Math.min(buf.length, count)); - const nwritten = await conn.write(buf); - assertStrictEquals(nwritten, buf.length); - count -= nwritten; - } - } - - async function receiveBytes( - conn: Deno.Conn, - byte: number, - count: number, - ) { - let buf = new Uint8Array(1 << 12 /* 4 kB */); - while (count > 0) { - buf = buf.subarray(0, Math.min(buf.length, count)); - const r = await conn.read(buf); - assertNotEquals(r, null); - assert(buf.subarray(0, r!).every((b) => b === byte)); - count -= r!; - } - } - - async function receiveEof(conn: Deno.Conn) { - const buf = new Uint8Array(1); - const r = await conn.read(buf); - assertStrictEquals(r, null); - } - - async function forwardBytes( - source: Deno.Conn, - sink: Deno.Conn, - count: number, - interruptPromise: ReturnType<typeof Promise.withResolvers<void>>, - ) { - let buf = new Uint8Array(1 << 12 /* 4 kB */); - while (count > 0) { - buf = buf.subarray(0, Math.min(buf.length, count)); - const nread = await Promise.race([ - source.read(buf), - interruptPromise.promise, - ]); - if (nread == null) break; // Either EOF or interrupted. - const nwritten = await sink.write(buf.subarray(0, nread)); - assertStrictEquals(nread, nwritten); - count -= nwritten; - } - } -} - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsHandshakeWithTcpCorruptionImmediately() { - await tlsWithTcpFailureTestImpl("handshake", 0, "corruption", false); - await tlsWithTcpFailureTestImpl("handshake", 0, "corruption", true); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsHandshakeWithTcpShutdownImmediately() { - await tlsWithTcpFailureTestImpl("handshake", 0, "shutdown", false); - await tlsWithTcpFailureTestImpl("handshake", 0, "shutdown", true); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsHandshakeWithTcpCorruptionAfter70Bytes() { - await tlsWithTcpFailureTestImpl("handshake", 76, "corruption", false); - await tlsWithTcpFailureTestImpl("handshake", 78, "corruption", true); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsHandshakeWithTcpShutdownAfter70bytes() { - await tlsWithTcpFailureTestImpl("handshake", 77, "shutdown", false); - await tlsWithTcpFailureTestImpl("handshake", 79, "shutdown", true); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsHandshakeWithTcpCorruptionAfter200Bytes() { - await tlsWithTcpFailureTestImpl("handshake", 200, "corruption", false); - await tlsWithTcpFailureTestImpl("handshake", 202, "corruption", true); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsHandshakeWithTcpShutdownAfter200bytes() { - await tlsWithTcpFailureTestImpl("handshake", 201, "shutdown", false); - await tlsWithTcpFailureTestImpl("handshake", 203, "shutdown", true); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsTrafficWithTcpCorruption() { - await tlsWithTcpFailureTestImpl("traffic", Infinity, "corruption", false); - await tlsWithTcpFailureTestImpl("traffic", Infinity, "corruption", true); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsTrafficWithTcpShutdown() { - await tlsWithTcpFailureTestImpl("traffic", Infinity, "shutdown", false); - await tlsWithTcpFailureTestImpl("traffic", Infinity, "shutdown", true); - }, -); - -function createHttpsListener(port: number): Deno.Listener { - // Query format: `curl --insecure https://localhost:8443/z/12345` - // The server returns a response consisting of 12345 times the letter 'z'. - const listener = Deno.listenTls({ - hostname: "localhost", - port, - cert: Deno.readTextFileSync("./cli/tests/testdata/tls/localhost.crt"), - key: Deno.readTextFileSync("./cli/tests/testdata/tls/localhost.key"), - }); - - serve(listener); - return listener; - - async function serve(listener: Deno.Listener) { - for await (const conn of listener) { - const EOL = "\r\n"; - - // Read GET request plus headers. - const buf = new Uint8Array(1 << 12 /* 4 kB */); - const decoder = new TextDecoder(); - let req = ""; - while (!req.endsWith(EOL + EOL)) { - const n = await conn.read(buf); - if (n === null) throw new Error("Unexpected EOF"); - req += decoder.decode(buf.subarray(0, n)); - } - - // Parse GET request. - const { filler, count, version } = - /^GET \/(?<filler>[^\/]+)\/(?<count>\d+) HTTP\/(?<version>1\.\d)\r\n/ - .exec(req)!.groups as { - filler: string; - count: string; - version: string; - }; - - // Generate response. - const resBody = new TextEncoder().encode(filler.repeat(+count)); - const resHead = new TextEncoder().encode( - [ - `HTTP/${version} 200 OK`, - `Content-Length: ${resBody.length}`, - "Content-Type: text/plain", - ].join(EOL) + EOL + EOL, - ); - - // Send response. - await writeAll(conn, resHead); - await writeAll(conn, resBody); - - // Close TCP connection. - conn.close(); - } - } -} - -async function curl(url: string): Promise<string> { - const { success, code, stdout, stderr } = await new Deno.Command("curl", { - args: ["--insecure", url], - }).output(); - - if (!success) { - throw new Error( - `curl ${url} failed: ${code}:\n${new TextDecoder().decode(stderr)}`, - ); - } - return new TextDecoder().decode(stdout); -} - -Deno.test( - { permissions: { read: true, net: true, run: true } }, - async function curlFakeHttpsServer() { - const port = getPort(); - const listener = createHttpsListener(port); - - const res1 = await curl(`https://localhost:${port}/d/1`); - assertStrictEquals(res1, "d"); - - const res2 = await curl(`https://localhost:${port}/e/12345`); - assertStrictEquals(res2, "e".repeat(12345)); - - const count3 = 1 << 17; // 128 kB. - const res3 = await curl(`https://localhost:${port}/n/${count3}`); - assertStrictEquals(res3, "n".repeat(count3)); - - const count4 = 12345678; - const res4 = await curl(`https://localhost:${port}/o/${count4}`); - assertStrictEquals(res4, "o".repeat(count4)); - - listener.close(); - }, -); - -Deno.test( - // Ignored because gmail appears to reject us on CI sometimes - { ignore: true, permissions: { read: true, net: true } }, - async function startTls() { - const hostname = "smtp.gmail.com"; - const port = 587; - const encoder = new TextEncoder(); - - const conn = await Deno.connect({ - hostname, - port, - }); - - let writer = new BufWriter(conn); - let reader = new TextProtoReader(new BufReader(conn)); - - let line: string | null = (await reader.readLine()) as string; - assert(line.startsWith("220")); - - await writer.write(encoder.encode(`EHLO ${hostname}\r\n`)); - await writer.flush(); - - while ((line = (await reader.readLine()) as string)) { - assert(line.startsWith("250")); - if (line.startsWith("250 ")) break; - } - - await writer.write(encoder.encode("STARTTLS\r\n")); - await writer.flush(); - - line = await reader.readLine(); - - // Received the message that the server is ready to establish TLS - assertEquals(line, "220 2.0.0 Ready to start TLS"); - - const tlsConn = await Deno.startTls(conn, { hostname }); - writer = new BufWriter(tlsConn); - reader = new TextProtoReader(new BufReader(tlsConn)); - - // After that use TLS communication again - await writer.write(encoder.encode(`EHLO ${hostname}\r\n`)); - await writer.flush(); - - while ((line = (await reader.readLine()) as string)) { - assert(line.startsWith("250")); - if (line.startsWith("250 ")) break; - } - - tlsConn.close(); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function connectTLSBadClientCertPrivateKey(): Promise<void> { - await assertRejects(async () => { - await Deno.connectTls({ - hostname: "deno.land", - port: 443, - certChain: "bad data", - privateKey: await Deno.readTextFile( - "cli/tests/testdata/tls/localhost.key", - ), - }); - }, Deno.errors.InvalidData); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function connectTLSBadPrivateKey(): Promise<void> { - await assertRejects(async () => { - await Deno.connectTls({ - hostname: "deno.land", - port: 443, - certChain: await Deno.readTextFile( - "cli/tests/testdata/tls/localhost.crt", - ), - privateKey: "bad data", - }); - }, Deno.errors.InvalidData); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function connectTLSNotPrivateKey(): Promise<void> { - await assertRejects(async () => { - await Deno.connectTls({ - hostname: "deno.land", - port: 443, - certChain: await Deno.readTextFile( - "cli/tests/testdata/tls/localhost.crt", - ), - privateKey: "", - }); - }, Deno.errors.InvalidData); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function connectWithClientCert() { - // The test_server running on port 4552 responds with 'PASS' if client - // authentication was successful. Try it by running test_server and - // curl --key cli/tests/testdata/tls/localhost.key \ - // --cert cli/tests/testdata/tls/localhost.crt \ - // --cacert cli/tests/testdata/tls/RootCA.crt https://localhost:4552/ - const conn = await Deno.connectTls({ - hostname: "localhost", - port: 4552, - certChain: await Deno.readTextFile( - "cli/tests/testdata/tls/localhost.crt", - ), - privateKey: await Deno.readTextFile( - "cli/tests/testdata/tls/localhost.key", - ), - caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")], - }); - const result = decoder.decode(await readAll(conn)); - assertEquals(result, "PASS"); - conn.close(); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function connectTLSCaCerts() { - const conn = await Deno.connectTls({ - hostname: "localhost", - port: 4557, - caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")], - }); - const result = decoder.decode(await readAll(conn)); - assertEquals(result, "PASS"); - conn.close(); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function connectTLSCertFile() { - const conn = await Deno.connectTls({ - hostname: "localhost", - port: 4557, - certFile: "cli/tests/testdata/tls/RootCA.pem", - }); - const result = decoder.decode(await readAll(conn)); - assertEquals(result, "PASS"); - conn.close(); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function startTLSCaCerts() { - const plainConn = await Deno.connect({ - hostname: "localhost", - port: 4557, - }); - const conn = await Deno.startTls(plainConn, { - hostname: "localhost", - caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")], - }); - const result = decoder.decode(await readAll(conn)); - assertEquals(result, "PASS"); - conn.close(); - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsHandshakeSuccess() { - const hostname = "localhost"; - const port = getPort(); - - const listener = Deno.listenTls({ - hostname, - port, - cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"), - key: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"), - }); - const acceptPromise = listener.accept(); - const connectPromise = Deno.connectTls({ - hostname, - port, - certFile: "cli/tests/testdata/tls/RootCA.crt", - }); - const [conn1, conn2] = await Promise.all([acceptPromise, connectPromise]); - listener.close(); - - await Promise.all([conn1.handshake(), conn2.handshake()]); - - // Begin sending a 10mb blob over the TLS connection. - const whole = new Uint8Array(10 << 20); // 10mb. - whole.fill(42); - const sendPromise = writeAll(conn1, whole); - // Set up the other end to receive half of the large blob. - const half = new Uint8Array(whole.byteLength / 2); - const receivePromise = readFull(conn2, half); - - await conn1.handshake(); - await conn2.handshake(); - - // Finish receiving the first 5mb. - assertEquals(await receivePromise, half.length); - - // See that we can call `handshake()` in the middle of large reads and writes. - await conn1.handshake(); - await conn2.handshake(); - - // Receive second half of large blob. Wait for the send promise and check it. - assertEquals(await readFull(conn2, half), half.length); - await sendPromise; - - await conn1.handshake(); - await conn2.handshake(); - - await conn1.closeWrite(); - await conn2.closeWrite(); - - await conn1.handshake(); - await conn2.handshake(); - - conn1.close(); - conn2.close(); - - async function readFull(conn: Deno.Conn, buf: Uint8Array) { - let offset, n; - for (offset = 0; offset < buf.length; offset += n) { - n = await conn.read(buf.subarray(offset, buf.length)); - assert(n != null && n > 0); - } - return offset; - } - }, -); - -Deno.test( - { permissions: { read: true, net: true } }, - async function tlsHandshakeFailure() { - const hostname = "localhost"; - const port = getPort(); - - async function server() { - const listener = Deno.listenTls({ - hostname, - port, - cert: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.crt"), - key: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.key"), - }); - for await (const conn of listener) { - for (let i = 0; i < 10; i++) { - // Handshake fails because the client rejects the server certificate. - await assertRejects( - () => conn.handshake(), - Deno.errors.InvalidData, - "received fatal alert", - ); - } - conn.close(); - break; - } - } - - async function connectTlsClient() { - const conn = await Deno.connectTls({ hostname, port }); - // Handshake fails because the server presents a self-signed certificate. - await assertRejects( - () => conn.handshake(), - Deno.errors.InvalidData, - "invalid peer certificate: UnknownIssuer", - ); - conn.close(); - } - - await Promise.all([server(), connectTlsClient()]); - - async function startTlsClient() { - const tcpConn = await Deno.connect({ hostname, port }); - const tlsConn = await Deno.startTls(tcpConn, { - hostname: "foo.land", - caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")], - }); - // Handshake fails because hostname doesn't match the certificate. - await assertRejects( - () => tlsConn.handshake(), - Deno.errors.InvalidData, - "NotValidForName", - ); - tlsConn.close(); - } - - await Promise.all([server(), startTlsClient()]); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function listenTlsWithReuseAddr() { - const deferred1 = Promise.withResolvers<void>(); - const hostname = "localhost"; - const port = 3500; - - const listener1 = Deno.listenTls({ hostname, port, cert, key }); - - listener1.accept().then((conn) => { - conn.close(); - deferred1.resolve(); - }); - - const conn1 = await Deno.connectTls({ hostname, port, caCerts }); - conn1.close(); - await deferred1.promise; - listener1.close(); - - const deferred2 = Promise.withResolvers<void>(); - const listener2 = Deno.listenTls({ hostname, port, cert, key }); - - listener2.accept().then((conn) => { - conn.close(); - deferred2.resolve(); - }); - - const conn2 = await Deno.connectTls({ hostname, port, caCerts }); - conn2.close(); - await deferred2.promise; - listener2.close(); - }, -); - -Deno.test({ - ignore: Deno.build.os !== "linux", - permissions: { net: true }, -}, async function listenTlsReusePort() { - const hostname = "localhost"; - const port = 4003; - const listener1 = Deno.listenTls({ - hostname, - port, - cert, - key, - reusePort: true, - }); - const listener2 = Deno.listenTls({ - hostname, - port, - cert, - key, - reusePort: true, - }); - let p1; - let p2; - let listener1Recv = false; - let listener2Recv = false; - while (!listener1Recv || !listener2Recv) { - if (!p1) { - p1 = listener1.accept().then((conn) => { - conn.close(); - listener1Recv = true; - p1 = undefined; - }).catch(() => {}); - } - if (!p2) { - p2 = listener2.accept().then((conn) => { - conn.close(); - listener2Recv = true; - p2 = undefined; - }).catch(() => {}); - } - const conn = await Deno.connectTls({ hostname, port, caCerts }); - conn.close(); - await Promise.race([p1, p2]); - } - listener1.close(); - listener2.close(); -}); - -Deno.test({ - ignore: Deno.build.os === "linux", - permissions: { net: true }, -}, function listenTlsReusePortDoesNothing() { - const hostname = "localhost"; - const port = 4003; - const listener1 = Deno.listenTls({ - hostname, - port, - cert, - key, - reusePort: true, - }); - assertThrows(() => { - Deno.listenTls({ hostname, port, cert, key, reusePort: true }); - }, Deno.errors.AddrInUse); - listener1.close(); -}); - -Deno.test({ - permissions: { net: true }, -}, function listenTlsDoesNotThrowOnStringPort() { - const listener = Deno.listenTls({ - hostname: "localhost", - // @ts-ignore String port is not allowed by typing, but it shouldn't throw - // for backwards compatibility. - port: "0", - cert, - key, - }); - listener.close(); -}); - -Deno.test( - { permissions: { net: true, read: true } }, - function listenTLSInvalidCert() { - assertThrows(() => { - Deno.listenTls({ - hostname: "localhost", - port: 3500, - certFile: "cli/tests/testdata/tls/invalid.crt", - keyFile: "cli/tests/testdata/tls/localhost.key", - }); - }, Deno.errors.InvalidData); - }, -); - -Deno.test( - { permissions: { net: true, read: true } }, - function listenTLSInvalidKey() { - assertThrows(() => { - Deno.listenTls({ - hostname: "localhost", - port: 3500, - certFile: "cli/tests/testdata/tls/localhost.crt", - keyFile: "cli/tests/testdata/tls/invalid.key", - }); - }, Deno.errors.InvalidData); - }, -); diff --git a/cli/tests/unit/truncate_test.ts b/cli/tests/unit/truncate_test.ts deleted file mode 100644 index 95b76052d..000000000 --- a/cli/tests/unit/truncate_test.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals, assertRejects, assertThrows } from "./test_util.ts"; - -Deno.test( - { permissions: { read: true, write: true } }, - function ftruncateSyncSuccess() { - const filename = Deno.makeTempDirSync() + "/test_ftruncateSync.txt"; - using file = Deno.openSync(filename, { - create: true, - read: true, - write: true, - }); - - file.truncateSync(20); - assertEquals(Deno.readFileSync(filename).byteLength, 20); - file.truncateSync(5); - assertEquals(Deno.readFileSync(filename).byteLength, 5); - file.truncateSync(-5); - assertEquals(Deno.readFileSync(filename).byteLength, 0); - - Deno.removeSync(filename); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function ftruncateSuccess() { - const filename = Deno.makeTempDirSync() + "/test_ftruncate.txt"; - using file = await Deno.open(filename, { - create: true, - read: true, - write: true, - }); - - await file.truncate(20); - assertEquals((await Deno.readFile(filename)).byteLength, 20); - await file.truncate(5); - assertEquals((await Deno.readFile(filename)).byteLength, 5); - await file.truncate(-5); - assertEquals((await Deno.readFile(filename)).byteLength, 0); - - await Deno.remove(filename); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function truncateSyncSuccess() { - const filename = Deno.makeTempDirSync() + "/test_truncateSync.txt"; - Deno.writeFileSync(filename, new Uint8Array(5)); - Deno.truncateSync(filename, 20); - assertEquals(Deno.readFileSync(filename).byteLength, 20); - Deno.truncateSync(filename, 5); - assertEquals(Deno.readFileSync(filename).byteLength, 5); - Deno.truncateSync(filename, -5); - assertEquals(Deno.readFileSync(filename).byteLength, 0); - Deno.removeSync(filename); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function truncateSuccess() { - const filename = Deno.makeTempDirSync() + "/test_truncate.txt"; - await Deno.writeFile(filename, new Uint8Array(5)); - await Deno.truncate(filename, 20); - assertEquals((await Deno.readFile(filename)).byteLength, 20); - await Deno.truncate(filename, 5); - assertEquals((await Deno.readFile(filename)).byteLength, 5); - await Deno.truncate(filename, -5); - assertEquals((await Deno.readFile(filename)).byteLength, 0); - await Deno.remove(filename); - }, -); - -Deno.test({ permissions: { write: false } }, function truncateSyncPerm() { - assertThrows(() => { - Deno.truncateSync("/test_truncateSyncPermission.txt"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test({ permissions: { write: false } }, async function truncatePerm() { - await assertRejects(async () => { - await Deno.truncate("/test_truncatePermission.txt"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { permissions: { read: true, write: true } }, - function truncateSyncNotFound() { - const filename = "/badfile.txt"; - assertThrows( - () => { - Deno.truncateSync(filename); - }, - Deno.errors.NotFound, - `truncate '${filename}'`, - ); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function truncateSyncNotFound() { - const filename = "/badfile.txt"; - await assertRejects( - async () => { - await Deno.truncate(filename); - }, - Deno.errors.NotFound, - `truncate '${filename}'`, - ); - }, -); diff --git a/cli/tests/unit/tty_color_test.ts b/cli/tests/unit/tty_color_test.ts deleted file mode 100644 index 6f26891e3..000000000 --- a/cli/tests/unit/tty_color_test.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "./test_util.ts"; - -// Note tests for Deno.stdin.setRaw is in integration tests. - -Deno.test( - { permissions: { run: true, read: true } }, - async function noColorIfNotTty() { - const { stdout } = await new Deno.Command(Deno.execPath(), { - args: ["eval", "console.log(1)"], - }).output(); - const output = new TextDecoder().decode(stdout); - assertEquals(output, "1\n"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function denoNoColorIsNotAffectedByNonTty() { - const { stdout } = await new Deno.Command(Deno.execPath(), { - args: ["eval", "console.log(Deno.noColor)"], - }).output(); - const output = new TextDecoder().decode(stdout); - assertEquals(output, "false\n"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function denoNoColorTrueEmptyVar() { - const { stdout } = await new Deno.Command(Deno.execPath(), { - args: ["eval", "console.log(Deno.noColor)"], - env: { - // https://no-color.org/ -- should not be true when empty - NO_COLOR: "", - }, - }).output(); - const output = new TextDecoder().decode(stdout); - assertEquals(output, "false\n"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function denoNoColorTrueEmptyVar() { - const { stdout } = await new Deno.Command(Deno.execPath(), { - args: ["eval", "console.log(Deno.noColor)"], - env: { - NO_COLOR: "1", - }, - }).output(); - const output = new TextDecoder().decode(stdout); - assertEquals(output, "true\n"); - }, -); diff --git a/cli/tests/unit/tty_test.ts b/cli/tests/unit/tty_test.ts deleted file mode 100644 index 8ca9a5d5b..000000000 --- a/cli/tests/unit/tty_test.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert } from "./test_util.ts"; - -// Note tests for Deno.stdin.setRaw is in integration tests. - -Deno.test(function consoleSize() { - if (!Deno.stdout.isTerminal()) { - return; - } - const result = Deno.consoleSize(); - assert(typeof result.columns !== "undefined"); - assert(typeof result.rows !== "undefined"); -}); - -Deno.test({ permissions: { read: true } }, function isatty() { - // CI not under TTY, so cannot test stdin/stdout/stderr. - const f = Deno.openSync("cli/tests/testdata/assets/hello.txt"); - assert(!Deno.isatty(f.rid)); - f.close(); -}); - -Deno.test(function isattyError() { - let caught = false; - try { - // Absurdly large rid. - Deno.isatty(0x7fffffff); - } catch (e) { - caught = true; - assert(e instanceof Deno.errors.BadResource); - } - assert(caught); -}); diff --git a/cli/tests/unit/umask_test.ts b/cli/tests/unit/umask_test.ts deleted file mode 100644 index 0e97f0d35..000000000 --- a/cli/tests/unit/umask_test.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "./test_util.ts"; - -Deno.test( - { - ignore: Deno.build.os === "windows", - }, - function umaskSuccess() { - const prevMask = Deno.umask(0o020); - const newMask = Deno.umask(prevMask); - const finalMask = Deno.umask(); - assertEquals(newMask, 0o020); - assertEquals(finalMask, prevMask); - }, -); diff --git a/cli/tests/unit/url_search_params_test.ts b/cli/tests/unit/url_search_params_test.ts deleted file mode 100644 index c547ef938..000000000 --- a/cli/tests/unit/url_search_params_test.ts +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals } from "./test_util.ts"; - -Deno.test(function urlSearchParamsWithMultipleSpaces() { - const init = { str: "this string has spaces in it" }; - const searchParams = new URLSearchParams(init).toString(); - assertEquals(searchParams, "str=this+string+has+spaces+in+it"); -}); - -Deno.test(function urlSearchParamsWithExclamation() { - const init = [ - ["str", "hello, world!"], - ]; - const searchParams = new URLSearchParams(init).toString(); - assertEquals(searchParams, "str=hello%2C+world%21"); -}); - -Deno.test(function urlSearchParamsWithQuotes() { - const init = [ - ["str", "'hello world'"], - ]; - const searchParams = new URLSearchParams(init).toString(); - assertEquals(searchParams, "str=%27hello+world%27"); -}); - -Deno.test(function urlSearchParamsWithBracket() { - const init = [ - ["str", "(hello world)"], - ]; - const searchParams = new URLSearchParams(init).toString(); - assertEquals(searchParams, "str=%28hello+world%29"); -}); - -Deno.test(function urlSearchParamsWithTilde() { - const init = [ - ["str", "hello~world"], - ]; - const searchParams = new URLSearchParams(init).toString(); - assertEquals(searchParams, "str=hello%7Eworld"); -}); - -Deno.test(function urlSearchParamsInitString() { - const init = "c=4&a=2&b=3&%C3%A1=1"; - const searchParams = new URLSearchParams(init); - assert( - init === searchParams.toString(), - "The init query string does not match", - ); -}); - -Deno.test(function urlSearchParamsInitStringWithPlusCharacter() { - let params = new URLSearchParams("q=a+b"); - assertEquals(params.toString(), "q=a+b"); - assertEquals(params.get("q"), "a b"); - - params = new URLSearchParams("q=a+b+c"); - assertEquals(params.toString(), "q=a+b+c"); - assertEquals(params.get("q"), "a b c"); -}); - -Deno.test(function urlSearchParamsInitStringWithMalformedParams() { - // These test cases are copied from Web Platform Tests - // https://github.com/web-platform-tests/wpt/blob/54c6d64/url/urlsearchparams-constructor.any.js#L60-L80 - let params = new URLSearchParams("id=0&value=%"); - assert(params != null, "constructor returned non-null value."); - assert(params.has("id"), 'Search params object has name "id"'); - assert(params.has("value"), 'Search params object has name "value"'); - assertEquals(params.get("id"), "0"); - assertEquals(params.get("value"), "%"); - - params = new URLSearchParams("b=%2sf%2a"); - assert(params != null, "constructor returned non-null value."); - assert(params.has("b"), 'Search params object has name "b"'); - assertEquals(params.get("b"), "%2sf*"); - - params = new URLSearchParams("b=%2%2af%2a"); - assert(params != null, "constructor returned non-null value."); - assert(params.has("b"), 'Search params object has name "b"'); - assertEquals(params.get("b"), "%2*f*"); - - params = new URLSearchParams("b=%%2a"); - assert(params != null, "constructor returned non-null value."); - assert(params.has("b"), 'Search params object has name "b"'); - assertEquals(params.get("b"), "%*"); -}); - -Deno.test(function urlSearchParamsInitIterable() { - const init = [ - ["a", "54"], - ["b", "true"], - ]; - const searchParams = new URLSearchParams(init); - assertEquals(searchParams.toString(), "a=54&b=true"); -}); - -Deno.test(function urlSearchParamsInitRecord() { - const init = { a: "54", b: "true" }; - const searchParams = new URLSearchParams(init); - assertEquals(searchParams.toString(), "a=54&b=true"); -}); - -Deno.test(function urlSearchParamsInit() { - const params1 = new URLSearchParams("a=b"); - assertEquals(params1.toString(), "a=b"); - const params2 = new URLSearchParams(params1); - assertEquals(params2.toString(), "a=b"); -}); - -Deno.test(function urlSearchParamsAppendSuccess() { - const searchParams = new URLSearchParams(); - searchParams.append("a", "true"); - assertEquals(searchParams.toString(), "a=true"); -}); - -Deno.test(function urlSearchParamsDeleteSuccess() { - const init = "a=54&b=true"; - const searchParams = new URLSearchParams(init); - searchParams.delete("b"); - assertEquals(searchParams.toString(), "a=54"); -}); - -Deno.test(function urlSearchParamsGetAllSuccess() { - const init = "a=54&b=true&a=true"; - const searchParams = new URLSearchParams(init); - assertEquals(searchParams.getAll("a"), ["54", "true"]); - assertEquals(searchParams.getAll("b"), ["true"]); - assertEquals(searchParams.getAll("c"), []); -}); - -Deno.test(function urlSearchParamsGetSuccess() { - const init = "a=54&b=true&a=true"; - const searchParams = new URLSearchParams(init); - assertEquals(searchParams.get("a"), "54"); - assertEquals(searchParams.get("b"), "true"); - assertEquals(searchParams.get("c"), null); -}); - -Deno.test(function urlSearchParamsHasSuccess() { - const init = "a=54&b=true&a=true"; - const searchParams = new URLSearchParams(init); - assert(searchParams.has("a")); - assert(searchParams.has("b")); - assert(!searchParams.has("c")); -}); - -Deno.test(function urlSearchParamsSetReplaceFirstAndRemoveOthers() { - const init = "a=54&b=true&a=true"; - const searchParams = new URLSearchParams(init); - searchParams.set("a", "false"); - assertEquals(searchParams.toString(), "a=false&b=true"); -}); - -Deno.test(function urlSearchParamsSetAppendNew() { - const init = "a=54&b=true&a=true"; - const searchParams = new URLSearchParams(init); - searchParams.set("c", "foo"); - assertEquals(searchParams.toString(), "a=54&b=true&a=true&c=foo"); -}); - -Deno.test(function urlSearchParamsSortSuccess() { - const init = "c=4&a=2&b=3&a=1"; - const searchParams = new URLSearchParams(init); - searchParams.sort(); - assertEquals(searchParams.toString(), "a=2&a=1&b=3&c=4"); -}); - -Deno.test(function urlSearchParamsForEachSuccess() { - const init = [ - ["a", "54"], - ["b", "true"], - ]; - const searchParams = new URLSearchParams(init); - let callNum = 0; - searchParams.forEach((value, key, parent) => { - assertEquals(searchParams, parent); - assertEquals(value, init[callNum][1]); - assertEquals(key, init[callNum][0]); - callNum++; - }); - assertEquals(callNum, init.length); -}); - -Deno.test(function urlSearchParamsMissingName() { - const init = "=4"; - const searchParams = new URLSearchParams(init); - assertEquals(searchParams.get(""), "4"); - assertEquals(searchParams.toString(), "=4"); -}); - -Deno.test(function urlSearchParamsMissingValue() { - const init = "4="; - const searchParams = new URLSearchParams(init); - assertEquals(searchParams.get("4"), ""); - assertEquals(searchParams.toString(), "4="); -}); - -Deno.test(function urlSearchParamsMissingEqualSign() { - const init = "4"; - const searchParams = new URLSearchParams(init); - assertEquals(searchParams.get("4"), ""); - assertEquals(searchParams.toString(), "4="); -}); - -Deno.test(function urlSearchParamsMissingPair() { - const init = "c=4&&a=54&"; - const searchParams = new URLSearchParams(init); - assertEquals(searchParams.toString(), "c=4&a=54"); -}); - -Deno.test(function urlSearchParamsForShortEncodedChar() { - const init = { linefeed: "\n", tab: "\t" }; - const searchParams = new URLSearchParams(init); - assertEquals(searchParams.toString(), "linefeed=%0A&tab=%09"); -}); - -// If pair does not contain exactly two items, then throw a TypeError. -// ref https://url.spec.whatwg.org/#interface-urlsearchparams -Deno.test(function urlSearchParamsShouldThrowTypeError() { - let hasThrown = 0; - - try { - new URLSearchParams([["1"]]); - hasThrown = 1; - } catch (err) { - if (err instanceof TypeError) { - hasThrown = 2; - } else { - hasThrown = 3; - } - } - - assertEquals(hasThrown, 2); - - try { - new URLSearchParams([["1", "2", "3"]]); - hasThrown = 1; - } catch (err) { - if (err instanceof TypeError) { - hasThrown = 2; - } else { - hasThrown = 3; - } - } - - assertEquals(hasThrown, 2); -}); - -Deno.test(function urlSearchParamsAppendArgumentsCheck() { - const methodRequireOneParam = ["delete", "getAll", "get", "has", "forEach"]; - - const methodRequireTwoParams = ["append", "set"]; - - methodRequireOneParam - .concat(methodRequireTwoParams) - .forEach((method: string) => { - const searchParams = new URLSearchParams(); - let hasThrown = 0; - try { - // deno-lint-ignore no-explicit-any - (searchParams as any)[method](); - hasThrown = 1; - } catch (err) { - if (err instanceof TypeError) { - hasThrown = 2; - } else { - hasThrown = 3; - } - } - assertEquals(hasThrown, 2); - }); - - methodRequireTwoParams.forEach((method: string) => { - const searchParams = new URLSearchParams(); - let hasThrown = 0; - try { - // deno-lint-ignore no-explicit-any - (searchParams as any)[method]("foo"); - hasThrown = 1; - } catch (err) { - if (err instanceof TypeError) { - hasThrown = 2; - } else { - hasThrown = 3; - } - } - assertEquals(hasThrown, 2); - }); -}); - -// ref: https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-delete.any.js -Deno.test(function urlSearchParamsDeletingAppendedMultiple() { - const params = new URLSearchParams(); - params.append("first", (1 as unknown) as string); - assert(params.has("first")); - assertEquals(params.get("first"), "1"); - params.delete("first"); - assertEquals(params.has("first"), false); - params.append("first", (1 as unknown) as string); - params.append("first", (10 as unknown) as string); - params.delete("first"); - assertEquals(params.has("first"), false); -}); - -// ref: https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-constructor.any.js#L176-L182 -Deno.test(function urlSearchParamsCustomSymbolIterator() { - const params = new URLSearchParams(); - params[Symbol.iterator] = function* (): IterableIterator<[string, string]> { - yield ["a", "b"]; - }; - const params1 = new URLSearchParams((params as unknown) as string[][]); - assertEquals(params1.get("a"), "b"); -}); - -Deno.test( - function urlSearchParamsCustomSymbolIteratorWithNonStringParams() { - const params = {}; - // deno-lint-ignore no-explicit-any - (params as any)[Symbol.iterator] = function* (): IterableIterator< - [number, number] - > { - yield [1, 2]; - }; - const params1 = new URLSearchParams((params as unknown) as string[][]); - assertEquals(params1.get("1"), "2"); - }, -); - -// If a class extends URLSearchParams, override one method should not change another's behavior. -Deno.test( - function urlSearchParamsOverridingAppendNotChangeConstructorAndSet() { - let overriddenAppendCalled = 0; - class CustomSearchParams extends URLSearchParams { - append(name: string, value: string) { - ++overriddenAppendCalled; - super.append(name, value); - } - } - new CustomSearchParams("foo=bar"); - new CustomSearchParams([["foo", "bar"]]); - new CustomSearchParams(new CustomSearchParams({ foo: "bar" })); - new CustomSearchParams().set("foo", "bar"); - assertEquals(overriddenAppendCalled, 0); - }, -); - -Deno.test(function urlSearchParamsOverridingEntriesNotChangeForEach() { - class CustomSearchParams extends URLSearchParams { - *entries(): IterableIterator<[string, string]> { - yield* []; - } - } - let loopCount = 0; - const params = new CustomSearchParams({ foo: "bar" }); - params.forEach(() => void ++loopCount); - assertEquals(loopCount, 1); -}); diff --git a/cli/tests/unit/url_test.ts b/cli/tests/unit/url_test.ts deleted file mode 100644 index b0dc86232..000000000 --- a/cli/tests/unit/url_test.ts +++ /dev/null @@ -1,529 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertStrictEquals, - assertThrows, -} from "./test_util.ts"; - -Deno.test(function urlParsing() { - const url = new URL( - "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat", - ); - assertEquals(url.hash, "#qat"); - assertEquals(url.host, "baz.qat:8000"); - assertEquals(url.hostname, "baz.qat"); - assertEquals( - url.href, - "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat", - ); - assertEquals(url.origin, "https://baz.qat:8000"); - assertEquals(url.password, "bar"); - assertEquals(url.pathname, "/qux/quux"); - assertEquals(url.port, "8000"); - assertEquals(url.protocol, "https:"); - assertEquals(url.search, "?foo=bar&baz=12"); - assertEquals(url.searchParams.getAll("foo"), ["bar"]); - assertEquals(url.searchParams.getAll("baz"), ["12"]); - assertEquals(url.username, "foo"); - assertEquals( - String(url), - "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat", - ); -}); - -Deno.test(function emptyUrl() { - assertThrows( - // @ts-ignore for test - () => new URL(), - TypeError, - "1 argument required, but only 0 present", - ); - assertThrows( - // @ts-ignore for test - () => URL.canParse(), - TypeError, - "1 argument required, but only 0 present", - ); -}); - -Deno.test(function urlProtocolParsing() { - assertEquals(new URL("Aa+-.1://foo").protocol, "aa+-.1:"); - assertEquals(new URL("aA+-.1://foo").protocol, "aa+-.1:"); - assertThrows(() => new URL("1://foo"), TypeError, "Invalid URL: '1://foo'"); - assertThrows(() => new URL("+://foo"), TypeError, "Invalid URL: '+://foo'"); - assertThrows(() => new URL("-://foo"), TypeError, "Invalid URL: '-://foo'"); - assertThrows(() => new URL(".://foo"), TypeError, "Invalid URL: '.://foo'"); - assertThrows(() => new URL("_://foo"), TypeError, "Invalid URL: '_://foo'"); - assertThrows(() => new URL("=://foo"), TypeError, "Invalid URL: '=://foo'"); - assertThrows(() => new URL("!://foo"), TypeError, "Invalid URL: '!://foo'"); - assertThrows(() => new URL(`"://foo`), TypeError, `Invalid URL: '"://foo'`); - assertThrows(() => new URL("$://foo"), TypeError, "Invalid URL: '$://foo'"); - assertThrows(() => new URL("%://foo"), TypeError, "Invalid URL: '%://foo'"); - assertThrows(() => new URL("^://foo"), TypeError, "Invalid URL: '^://foo'"); - assertThrows(() => new URL("*://foo"), TypeError, "Invalid URL: '*://foo'"); - assertThrows(() => new URL("*://foo"), TypeError, "Invalid URL: '*://foo'"); - assertThrows( - () => new URL("!:", "*://foo"), - TypeError, - "Invalid URL: '!:' with base '*://foo'", - ); -}); - -Deno.test(function urlAuthenticationParsing() { - const specialUrl = new URL("http://foo:bar@baz"); - assertEquals(specialUrl.username, "foo"); - assertEquals(specialUrl.password, "bar"); - assertEquals(specialUrl.hostname, "baz"); - assertThrows(() => new URL("file://foo:bar@baz"), TypeError, "Invalid URL"); - const nonSpecialUrl = new URL("abcd://foo:bar@baz"); - assertEquals(nonSpecialUrl.username, "foo"); - assertEquals(nonSpecialUrl.password, "bar"); - assertEquals(nonSpecialUrl.hostname, "baz"); -}); - -Deno.test(function urlHostnameParsing() { - // IPv6. - assertEquals(new URL("http://[::1]").hostname, "[::1]"); - assertEquals(new URL("file://[::1]").hostname, "[::1]"); - assertEquals(new URL("abcd://[::1]").hostname, "[::1]"); - assertEquals(new URL("http://[0:f:0:0:f:f:0:0]").hostname, "[0:f::f:f:0:0]"); - - // Forbidden host code point. - assertThrows(() => new URL("http:// a"), TypeError, "Invalid URL"); - assertThrows(() => new URL("file:// a"), TypeError, "Invalid URL"); - assertThrows(() => new URL("abcd:// a"), TypeError, "Invalid URL"); - assertThrows(() => new URL("http://%"), TypeError, "Invalid URL"); - assertThrows(() => new URL("file://%"), TypeError, "Invalid URL"); - assertEquals(new URL("abcd://%").hostname, "%"); - - // Percent-decode. - assertEquals(new URL("http://%21").hostname, "!"); - assertEquals(new URL("file://%21").hostname, "!"); - assertEquals(new URL("abcd://%21").hostname, "%21"); - - // IPv4 parsing. - assertEquals(new URL("http://260").hostname, "0.0.1.4"); - assertEquals(new URL("file://260").hostname, "0.0.1.4"); - assertEquals(new URL("abcd://260").hostname, "260"); - assertEquals(new URL("http://255.0.0.0").hostname, "255.0.0.0"); - assertThrows(() => new URL("http://256.0.0.0"), TypeError, "Invalid URL"); - assertEquals(new URL("http://0.255.0.0").hostname, "0.255.0.0"); - assertThrows(() => new URL("http://0.256.0.0"), TypeError, "Invalid URL"); - assertEquals(new URL("http://0.0.255.0").hostname, "0.0.255.0"); - assertThrows(() => new URL("http://0.0.256.0"), TypeError, "Invalid URL"); - assertEquals(new URL("http://0.0.0.255").hostname, "0.0.0.255"); - assertThrows(() => new URL("http://0.0.0.256"), TypeError, "Invalid URL"); - assertEquals(new URL("http://0.0.65535").hostname, "0.0.255.255"); - assertThrows(() => new URL("http://0.0.65536"), TypeError, "Invalid URL"); - assertEquals(new URL("http://0.16777215").hostname, "0.255.255.255"); - assertThrows(() => new URL("http://0.16777216"), TypeError, "Invalid URL"); - assertEquals(new URL("http://4294967295").hostname, "255.255.255.255"); - assertThrows(() => new URL("http://4294967296"), TypeError, "Invalid URL"); -}); - -Deno.test(function urlPortParsing() { - const specialUrl = new URL("http://foo:8000"); - assertEquals(specialUrl.hostname, "foo"); - assertEquals(specialUrl.port, "8000"); - assertThrows(() => new URL("file://foo:8000"), TypeError, "Invalid URL"); - const nonSpecialUrl = new URL("abcd://foo:8000"); - assertEquals(nonSpecialUrl.hostname, "foo"); - assertEquals(nonSpecialUrl.port, "8000"); -}); - -Deno.test(function urlModifications() { - const url = new URL( - "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat", - ); - url.hash = ""; - assertEquals( - url.href, - "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12", - ); - url.host = "qat.baz:8080"; - assertEquals( - url.href, - "https://foo:bar@qat.baz:8080/qux/quux?foo=bar&baz=12", - ); - url.hostname = "foo.bar"; - assertEquals( - url.href, - "https://foo:bar@foo.bar:8080/qux/quux?foo=bar&baz=12", - ); - url.password = "qux"; - assertEquals( - url.href, - "https://foo:qux@foo.bar:8080/qux/quux?foo=bar&baz=12", - ); - url.pathname = "/foo/bar%qat"; - assertEquals( - url.href, - "https://foo:qux@foo.bar:8080/foo/bar%qat?foo=bar&baz=12", - ); - url.port = ""; - assertEquals(url.href, "https://foo:qux@foo.bar/foo/bar%qat?foo=bar&baz=12"); - url.protocol = "http:"; - assertEquals(url.href, "http://foo:qux@foo.bar/foo/bar%qat?foo=bar&baz=12"); - url.search = "?foo=bar&foo=baz"; - assertEquals(url.href, "http://foo:qux@foo.bar/foo/bar%qat?foo=bar&foo=baz"); - assertEquals(url.searchParams.getAll("foo"), ["bar", "baz"]); - url.username = "foo@bar"; - assertEquals( - url.href, - "http://foo%40bar:qux@foo.bar/foo/bar%qat?foo=bar&foo=baz", - ); - url.searchParams.set("bar", "qat"); - assertEquals( - url.href, - "http://foo%40bar:qux@foo.bar/foo/bar%qat?foo=bar&foo=baz&bar=qat", - ); - url.searchParams.delete("foo"); - assertEquals(url.href, "http://foo%40bar:qux@foo.bar/foo/bar%qat?bar=qat"); - url.searchParams.append("foo", "bar"); - assertEquals( - url.href, - "http://foo%40bar:qux@foo.bar/foo/bar%qat?bar=qat&foo=bar", - ); -}); - -Deno.test(function urlModifyHref() { - const url = new URL("http://example.com/"); - url.href = "https://foo:bar@example.com:8080/baz/qat#qux"; - assertEquals(url.protocol, "https:"); - assertEquals(url.username, "foo"); - assertEquals(url.password, "bar"); - assertEquals(url.host, "example.com:8080"); - assertEquals(url.hostname, "example.com"); - assertEquals(url.pathname, "/baz/qat"); - assertEquals(url.hash, "#qux"); -}); - -Deno.test(function urlNormalize() { - const url = new URL("http://example.com"); - assertEquals(url.pathname, "/"); - assertEquals(url.href, "http://example.com/"); -}); - -Deno.test(function urlModifyPathname() { - const url = new URL("http://foo.bar/baz%qat/qux%quux"); - assertEquals(url.pathname, "/baz%qat/qux%quux"); - // Self-assignment is to invoke the setter. - // deno-lint-ignore no-self-assign - url.pathname = url.pathname; - assertEquals(url.pathname, "/baz%qat/qux%quux"); - url.pathname = "baz#qat qux"; - assertEquals(url.pathname, "/baz%23qat%20qux"); - // deno-lint-ignore no-self-assign - url.pathname = url.pathname; - assertEquals(url.pathname, "/baz%23qat%20qux"); - url.pathname = "\\a\\b\\c"; - assertEquals(url.pathname, "/a/b/c"); -}); - -Deno.test(function urlModifyHash() { - const url = new URL("http://foo.bar"); - url.hash = "%foo bar/qat%qux#bar"; - assertEquals(url.hash, "#%foo%20bar/qat%qux#bar"); - // deno-lint-ignore no-self-assign - url.hash = url.hash; - assertEquals(url.hash, "#%foo%20bar/qat%qux#bar"); -}); - -Deno.test(function urlSearchParamsReuse() { - const url = new URL( - "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat", - ); - const sp = url.searchParams; - url.host = "baz.qat"; - assert(sp === url.searchParams, "Search params should be reused."); -}); - -Deno.test(function urlBackSlashes() { - const url = new URL( - "https:\\\\foo:bar@baz.qat:8000\\qux\\quux?foo=bar&baz=12#qat", - ); - assertEquals( - url.href, - "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat", - ); -}); - -Deno.test(function urlProtocolSlashes() { - assertEquals(new URL("http:foo").href, "http://foo/"); - assertEquals(new URL("http://foo").href, "http://foo/"); - assertEquals(new URL("file:foo").href, "file:///foo"); - assertEquals(new URL("file://foo").href, "file://foo/"); - assertEquals(new URL("abcd:foo").href, "abcd:foo"); - assertEquals(new URL("abcd://foo").href, "abcd://foo"); -}); - -Deno.test(function urlRequireHost() { - assertEquals(new URL("file:///").href, "file:///"); - assertThrows(() => new URL("ftp:///"), TypeError, "Invalid URL"); - assertThrows(() => new URL("http:///"), TypeError, "Invalid URL"); - assertThrows(() => new URL("https:///"), TypeError, "Invalid URL"); - assertThrows(() => new URL("ws:///"), TypeError, "Invalid URL"); - assertThrows(() => new URL("wss:///"), TypeError, "Invalid URL"); -}); - -Deno.test(function urlDriveLetter() { - assertEquals(new URL("file:///C:").href, "file:///C:"); - assertEquals(new URL("file:///C:/").href, "file:///C:/"); - assertEquals(new URL("file:///C:/..").href, "file:///C:/"); - - // Don't recognise drive letters with extra leading slashes. - // FIXME(nayeemrmn): This is true according to - // https://jsdom.github.io/whatwg-url/#url=ZmlsZTovLy8vQzovLi4=&base=ZmlsZTovLy8= - // but not the behavior of rust-url. - // assertEquals(new URL("file:////C:/..").href, "file:///"); - - // Drop the hostname if a drive letter is parsed. - assertEquals(new URL("file://foo/C:").href, "file:///C:"); - - // Don't recognise drive letters in non-file protocols. - // FIXME(nayeemrmn): This is true according to - // https://jsdom.github.io/whatwg-url/#url=YWJjZDovL2Zvby9DOi8uLg==&base=ZmlsZTovLy8= - // but not the behavior of rust-url. - // assertEquals(new URL("http://foo/C:/..").href, "http://foo/"); - // assertEquals(new URL("abcd://foo/C:/..").href, "abcd://foo/"); -}); - -Deno.test(function urlHostnameUpperCase() { - assertEquals(new URL("http://EXAMPLE.COM").href, "http://example.com/"); - assertEquals(new URL("abcd://EXAMPLE.COM").href, "abcd://EXAMPLE.COM"); -}); - -Deno.test(function urlEmptyPath() { - assertEquals(new URL("http://foo").pathname, "/"); - assertEquals(new URL("file://foo").pathname, "/"); - assertEquals(new URL("abcd://foo").pathname, ""); -}); - -Deno.test(function urlPathRepeatedSlashes() { - assertEquals(new URL("http://foo//bar//").pathname, "//bar//"); - assertEquals(new URL("file://foo///bar//").pathname, "/bar//"); - assertEquals(new URL("abcd://foo//bar//").pathname, "//bar//"); -}); - -Deno.test(function urlTrim() { - assertEquals(new URL(" http://example.com ").href, "http://example.com/"); -}); - -Deno.test(function urlEncoding() { - assertEquals( - new URL("http://a !$&*()=,;+'\"@example.com").username, - "a%20!$&*()%3D,%3B+'%22", - ); - assertEquals( - new URL("http://:a !$&*()=,;+'\"@example.com").password, - "a%20!$&*()%3D,%3B+'%22", - ); - // https://url.spec.whatwg.org/#idna - assertEquals(new URL("http://mañana/c?d#e").hostname, "xn--maana-pta"); - assertEquals(new URL("abcd://mañana/c?d#e").hostname, "ma%C3%B1ana"); - assertEquals( - new URL("http://example.com/a ~!@$&*()=:/,;+'\"\\").pathname, - "/a%20~!@$&*()=:/,;+'%22/", - ); - assertEquals( - new URL("http://example.com?a ~!@$&*()=:/,;?+'\"\\").search, - "?a%20~!@$&*()=:/,;?+%27%22\\", - ); - assertEquals( - new URL("abcd://example.com?a ~!@$&*()=:/,;?+'\"\\").search, - "?a%20~!@$&*()=:/,;?+'%22\\", - ); - assertEquals( - new URL("http://example.com#a ~!@#$&*()=:/,;?+'\"\\").hash, - "#a%20~!@#$&*()=:/,;?+'%22\\", - ); -}); - -Deno.test(function urlBase() { - assertEquals(new URL("d", new URL("http://foo/a?b#c")).href, "http://foo/d"); - - assertEquals(new URL("", "http://foo/a/b?c#d").href, "http://foo/a/b?c"); - assertEquals(new URL("", "file://foo/a/b?c#d").href, "file://foo/a/b?c"); - assertEquals(new URL("", "abcd://foo/a/b?c#d").href, "abcd://foo/a/b?c"); - - assertEquals(new URL("#e", "http://foo/a/b?c#d").href, "http://foo/a/b?c#e"); - assertEquals(new URL("#e", "file://foo/a/b?c#d").href, "file://foo/a/b?c#e"); - assertEquals(new URL("#e", "abcd://foo/a/b?c#d").href, "abcd://foo/a/b?c#e"); - - assertEquals(new URL("?e", "http://foo/a/b?c#d").href, "http://foo/a/b?e"); - assertEquals(new URL("?e", "file://foo/a/b?c#d").href, "file://foo/a/b?e"); - assertEquals(new URL("?e", "abcd://foo/a/b?c#d").href, "abcd://foo/a/b?e"); - - assertEquals(new URL("e", "http://foo/a/b?c#d").href, "http://foo/a/e"); - assertEquals(new URL("e", "file://foo/a/b?c#d").href, "file://foo/a/e"); - assertEquals(new URL("e", "abcd://foo/a/b?c#d").href, "abcd://foo/a/e"); - - assertEquals(new URL(".", "http://foo/a/b?c#d").href, "http://foo/a/"); - assertEquals(new URL(".", "file://foo/a/b?c#d").href, "file://foo/a/"); - assertEquals(new URL(".", "abcd://foo/a/b?c#d").href, "abcd://foo/a/"); - - assertEquals(new URL("..", "http://foo/a/b?c#d").href, "http://foo/"); - assertEquals(new URL("..", "file://foo/a/b?c#d").href, "file://foo/"); - assertEquals(new URL("..", "abcd://foo/a/b?c#d").href, "abcd://foo/"); - - assertEquals(new URL("/e", "http://foo/a/b?c#d").href, "http://foo/e"); - assertEquals(new URL("/e", "file://foo/a/b?c#d").href, "file://foo/e"); - assertEquals(new URL("/e", "abcd://foo/a/b?c#d").href, "abcd://foo/e"); - - assertEquals(new URL("//bar", "http://foo/a/b?c#d").href, "http://bar/"); - assertEquals(new URL("//bar", "file://foo/a/b?c#d").href, "file://bar/"); - assertEquals(new URL("//bar", "abcd://foo/a/b?c#d").href, "abcd://bar"); - - assertEquals(new URL("efgh:", "http://foo/a/b?c#d").href, "efgh:"); - assertEquals(new URL("efgh:", "file://foo/a/b?c#d").href, "efgh:"); - assertEquals(new URL("efgh:", "abcd://foo/a/b?c#d").href, "efgh:"); - - assertEquals(new URL("/foo", "abcd:/").href, "abcd:/foo"); -}); - -Deno.test(function urlDriveLetterBase() { - assertEquals(new URL("/b", "file:///C:/a/b").href, "file:///C:/b"); - assertEquals(new URL("/D:", "file:///C:/a/b").href, "file:///D:"); -}); - -Deno.test(function urlSameProtocolBase() { - assertEquals(new URL("http:", "http://foo/a").href, "http://foo/a"); - assertEquals(new URL("file:", "file://foo/a").href, "file://foo/a"); - assertEquals(new URL("abcd:", "abcd://foo/a").href, "abcd:"); - - assertEquals(new URL("http:b", "http://foo/a").href, "http://foo/b"); - assertEquals(new URL("file:b", "file://foo/a").href, "file://foo/b"); - assertEquals(new URL("abcd:b", "abcd://foo/a").href, "abcd:b"); -}); - -Deno.test(function deletingAllParamsRemovesQuestionMarkFromURL() { - const url = new URL("http://example.com/?param1¶m2"); - url.searchParams.delete("param1"); - url.searchParams.delete("param2"); - assertEquals(url.href, "http://example.com/"); - assertEquals(url.search, ""); -}); - -Deno.test(function removingNonExistentParamRemovesQuestionMarkFromURL() { - const url = new URL("http://example.com/?"); - assertEquals(url.href, "http://example.com/?"); - url.searchParams.delete("param1"); - assertEquals(url.href, "http://example.com/"); - assertEquals(url.search, ""); -}); - -Deno.test(function sortingNonExistentParamRemovesQuestionMarkFromURL() { - const url = new URL("http://example.com/?"); - assertEquals(url.href, "http://example.com/?"); - url.searchParams.sort(); - assertEquals(url.href, "http://example.com/"); - assertEquals(url.search, ""); -}); - -Deno.test(function customInspectFunction() { - const url = new URL("http://example.com/?"); - assertEquals( - Deno.inspect(url), - `URL { - href: "http://example.com/?", - origin: "http://example.com", - protocol: "http:", - username: "", - password: "", - host: "example.com", - hostname: "example.com", - port: "", - pathname: "/", - hash: "", - search: "" -}`, - ); -}); - -Deno.test(function protocolNotHttpOrFile() { - const url = new URL("about:blank"); - assertEquals(url.href, "about:blank"); - assertEquals(url.protocol, "about:"); - assertEquals(url.origin, "null"); -}); - -Deno.test(function throwForInvalidPortConstructor() { - const urls = [ - // If port is greater than 2^16 − 1, validation error, return failure. - `https://baz.qat:${2 ** 16}`, - "https://baz.qat:-32", - "https://baz.qat:deno", - "https://baz.qat:9land", - "https://baz.qat:10.5", - ]; - - for (const url of urls) { - assertThrows(() => new URL(url), TypeError, "Invalid URL"); - } - - // Do not throw for 0 & 65535 - new URL("https://baz.qat:65535"); - new URL("https://baz.qat:0"); -}); - -Deno.test(function doNotOverridePortIfInvalid() { - const initialPort = "3000"; - const url = new URL(`https://deno.land:${initialPort}`); - // If port is greater than 2^16 − 1, validation error, return failure. - url.port = `${2 ** 16}`; - assertEquals(url.port, initialPort); -}); - -Deno.test(function emptyPortForSchemeDefaultPort() { - const nonDefaultPort = "3500"; - - const url = new URL("ftp://baz.qat:21"); - assertEquals(url.port, ""); - url.port = nonDefaultPort; - assertEquals(url.port, nonDefaultPort); - url.port = "21"; - assertEquals(url.port, ""); - url.protocol = "http"; - assertEquals(url.port, ""); - - const url2 = new URL("https://baz.qat:443"); - assertEquals(url2.port, ""); - url2.port = nonDefaultPort; - assertEquals(url2.port, nonDefaultPort); - url2.port = "443"; - assertEquals(url2.port, ""); - url2.protocol = "http"; - assertEquals(url2.port, ""); -}); - -Deno.test(function assigningPortPropertyAffectsReceiverOnly() { - // Setting `.port` should update only the receiver. - const u1 = new URL("http://google.com/"); - // deno-lint-ignore no-explicit-any - const u2 = new URL(u1 as any); - u2.port = "123"; - assertStrictEquals(u1.port, ""); - assertStrictEquals(u2.port, "123"); -}); - -Deno.test(function urlSearchParamsIdentityPreserved() { - // URLSearchParams identity should not be lost when URL is updated. - const u = new URL("http://foo.com/"); - const sp1 = u.searchParams; - u.href = "http://bar.com/?baz=42"; - const sp2 = u.searchParams; - assertStrictEquals(sp1, sp2); -}); - -Deno.test(function urlTakeURLObjectAsParameter() { - const url = new URL( - new URL( - "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat", - ), - ); - assertEquals( - url.href, - "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat", - ); -}); diff --git a/cli/tests/unit/urlpattern_test.ts b/cli/tests/unit/urlpattern_test.ts deleted file mode 100644 index 7730dbe40..000000000 --- a/cli/tests/unit/urlpattern_test.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals } from "./test_util.ts"; -import { assertType, IsExact } from "@test_util/std/testing/types.ts"; - -Deno.test(function urlPatternFromString() { - const pattern = new URLPattern("https://deno.land/foo/:bar"); - assertEquals(pattern.protocol, "https"); - assertEquals(pattern.hostname, "deno.land"); - assertEquals(pattern.pathname, "/foo/:bar"); - - assert(pattern.test("https://deno.land/foo/x")); - assert(!pattern.test("https://deno.com/foo/x")); - const match = pattern.exec("https://deno.land/foo/x"); - assert(match); - assertEquals(match.pathname.input, "/foo/x"); - assertEquals(match.pathname.groups, { bar: "x" }); - - // group values should be nullable - const val = match.pathname.groups.val; - assertType<IsExact<typeof val, string | undefined>>(true); -}); - -Deno.test(function urlPatternFromStringWithBase() { - const pattern = new URLPattern("/foo/:bar", "https://deno.land"); - assertEquals(pattern.protocol, "https"); - assertEquals(pattern.hostname, "deno.land"); - assertEquals(pattern.pathname, "/foo/:bar"); - - assert(pattern.test("https://deno.land/foo/x")); - assert(!pattern.test("https://deno.com/foo/x")); - const match = pattern.exec("https://deno.land/foo/x"); - assert(match); - assertEquals(match.pathname.input, "/foo/x"); - assertEquals(match.pathname.groups, { bar: "x" }); -}); - -Deno.test(function urlPatternFromInit() { - const pattern = new URLPattern({ - pathname: "/foo/:bar", - }); - assertEquals(pattern.protocol, "*"); - assertEquals(pattern.hostname, "*"); - assertEquals(pattern.pathname, "/foo/:bar"); - - assert(pattern.test("https://deno.land/foo/x")); - assert(pattern.test("https://deno.com/foo/x")); - assert(!pattern.test("https://deno.com/bar/x")); - - assert(pattern.test({ pathname: "/foo/x" })); -}); - -Deno.test(function urlPatternWithPrototypePollution() { - const originalExec = RegExp.prototype.exec; - try { - RegExp.prototype.exec = () => { - throw Error(); - }; - const pattern = new URLPattern({ - pathname: "/foo/:bar", - }); - assert(pattern.test("https://deno.land/foo/x")); - } finally { - RegExp.prototype.exec = originalExec; - } -}); diff --git a/cli/tests/unit/utime_test.ts b/cli/tests/unit/utime_test.ts deleted file mode 100644 index 9f5f25bee..000000000 --- a/cli/tests/unit/utime_test.ts +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assertEquals, - assertRejects, - assertThrows, - pathToAbsoluteFileUrl, -} from "./test_util.ts"; - -Deno.test( - { permissions: { read: true, write: true } }, - async function futimeSyncSuccess() { - const testDir = await Deno.makeTempDir(); - const filename = testDir + "/file.txt"; - using file = await Deno.open(filename, { - create: true, - write: true, - }); - - const atime = 1000; - const mtime = 50000; - await Deno.futime(file.rid, atime, mtime); - await file.syncData(); - - const fileInfo = Deno.statSync(filename); - assertEquals(fileInfo.atime, new Date(atime * 1000)); - assertEquals(fileInfo.mtime, new Date(mtime * 1000)); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function fsFileUtimeSyncSuccess() { - const testDir = await Deno.makeTempDir(); - const filename = testDir + "/file.txt"; - using file = await Deno.open(filename, { - create: true, - write: true, - }); - - const atime = 1000; - const mtime = 50000; - await file.utime(atime, mtime); - await file.syncData(); - - const fileInfo = Deno.statSync(filename); - assertEquals(fileInfo.atime, new Date(atime * 1000)); - assertEquals(fileInfo.mtime, new Date(mtime * 1000)); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function futimeSyncSuccess() { - const testDir = Deno.makeTempDirSync(); - const filename = testDir + "/file.txt"; - using file = Deno.openSync(filename, { - create: true, - write: true, - }); - - const atime = 1000; - const mtime = 50000; - Deno.futimeSync(file.rid, atime, mtime); - file.syncDataSync(); - - const fileInfo = Deno.statSync(filename); - assertEquals(fileInfo.atime, new Date(atime * 1000)); - assertEquals(fileInfo.mtime, new Date(mtime * 1000)); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function futimeSyncSuccess() { - const testDir = Deno.makeTempDirSync(); - const filename = testDir + "/file.txt"; - using file = Deno.openSync(filename, { - create: true, - write: true, - }); - - const atime = 1000; - const mtime = 50000; - file.utimeSync(atime, mtime); - file.syncDataSync(); - - const fileInfo = Deno.statSync(filename); - assertEquals(fileInfo.atime, new Date(atime * 1000)); - assertEquals(fileInfo.mtime, new Date(mtime * 1000)); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function utimeSyncFileSuccess() { - const testDir = Deno.makeTempDirSync(); - const filename = testDir + "/file.txt"; - Deno.writeFileSync(filename, new TextEncoder().encode("hello"), { - mode: 0o666, - }); - - const atime = 1000; - const mtime = 50000; - Deno.utimeSync(filename, atime, mtime); - - const fileInfo = Deno.statSync(filename); - assertEquals(fileInfo.atime, new Date(atime * 1000)); - assertEquals(fileInfo.mtime, new Date(mtime * 1000)); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function utimeSyncUrlSuccess() { - const testDir = Deno.makeTempDirSync(); - const filename = testDir + "/file.txt"; - Deno.writeFileSync(filename, new TextEncoder().encode("hello"), { - mode: 0o666, - }); - - const atime = 1000; - const mtime = 50000; - Deno.utimeSync(pathToAbsoluteFileUrl(filename), atime, mtime); - - const fileInfo = Deno.statSync(filename); - assertEquals(fileInfo.atime, new Date(atime * 1000)); - assertEquals(fileInfo.mtime, new Date(mtime * 1000)); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function utimeSyncDirectorySuccess() { - const testDir = Deno.makeTempDirSync(); - - const atime = 1000; - const mtime = 50000; - Deno.utimeSync(testDir, atime, mtime); - - const dirInfo = Deno.statSync(testDir); - assertEquals(dirInfo.atime, new Date(atime * 1000)); - assertEquals(dirInfo.mtime, new Date(mtime * 1000)); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function utimeSyncDateSuccess() { - const testDir = Deno.makeTempDirSync(); - - const atime = new Date(1000_000); - const mtime = new Date(50000_000); - Deno.utimeSync(testDir, atime, mtime); - - const dirInfo = Deno.statSync(testDir); - assertEquals(dirInfo.atime, atime); - assertEquals(dirInfo.mtime, mtime); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function utimeSyncFileDateSuccess() { - const testDir = Deno.makeTempDirSync(); - const filename = testDir + "/file.txt"; - Deno.writeFileSync(filename, new TextEncoder().encode("hello"), { - mode: 0o666, - }); - const atime = new Date(); - const mtime = new Date(); - Deno.utimeSync(filename, atime, mtime); - - const fileInfo = Deno.statSync(filename); - assertEquals(fileInfo.atime, atime); - assertEquals(fileInfo.mtime, mtime); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function utimeSyncLargeNumberSuccess() { - const testDir = Deno.makeTempDirSync(); - - // There are Rust side caps (might be fs relate), - // so JUST make them slightly larger than UINT32_MAX. - const atime = 0x100000001; - const mtime = 0x100000002; - Deno.utimeSync(testDir, atime, mtime); - - const dirInfo = Deno.statSync(testDir); - assertEquals(dirInfo.atime, new Date(atime * 1000)); - assertEquals(dirInfo.mtime, new Date(mtime * 1000)); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function utimeSyncNotFound() { - const atime = 1000; - const mtime = 50000; - - assertThrows( - () => { - Deno.utimeSync("/baddir", atime, mtime); - }, - Deno.errors.NotFound, - "utime '/baddir'", - ); - }, -); - -Deno.test( - { permissions: { read: true, write: false } }, - function utimeSyncPerm() { - const atime = 1000; - const mtime = 50000; - - assertThrows(() => { - Deno.utimeSync("/some_dir", atime, mtime); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function utimeFileSuccess() { - const testDir = Deno.makeTempDirSync(); - const filename = testDir + "/file.txt"; - Deno.writeFileSync(filename, new TextEncoder().encode("hello"), { - mode: 0o666, - }); - - const atime = 1000; - const mtime = 50000; - await Deno.utime(filename, atime, mtime); - - const fileInfo = Deno.statSync(filename); - assertEquals(fileInfo.atime, new Date(atime * 1000)); - assertEquals(fileInfo.mtime, new Date(mtime * 1000)); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function utimeUrlSuccess() { - const testDir = Deno.makeTempDirSync(); - const filename = testDir + "/file.txt"; - Deno.writeFileSync(filename, new TextEncoder().encode("hello"), { - mode: 0o666, - }); - - const atime = 1000; - const mtime = 50000; - await Deno.utime(pathToAbsoluteFileUrl(filename), atime, mtime); - - const fileInfo = Deno.statSync(filename); - assertEquals(fileInfo.atime, new Date(atime * 1000)); - assertEquals(fileInfo.mtime, new Date(mtime * 1000)); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function utimeDirectorySuccess() { - const testDir = Deno.makeTempDirSync(); - - const atime = 1000; - const mtime = 50000; - await Deno.utime(testDir, atime, mtime); - - const dirInfo = Deno.statSync(testDir); - assertEquals(dirInfo.atime, new Date(atime * 1000)); - assertEquals(dirInfo.mtime, new Date(mtime * 1000)); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function utimeDateSuccess() { - const testDir = Deno.makeTempDirSync(); - - const atime = new Date(100_000); - const mtime = new Date(5000_000); - await Deno.utime(testDir, atime, mtime); - - const dirInfo = Deno.statSync(testDir); - assertEquals(dirInfo.atime, atime); - assertEquals(dirInfo.mtime, mtime); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function utimeFileDateSuccess() { - const testDir = Deno.makeTempDirSync(); - const filename = testDir + "/file.txt"; - Deno.writeFileSync(filename, new TextEncoder().encode("hello"), { - mode: 0o666, - }); - - const atime = new Date(); - const mtime = new Date(); - await Deno.utime(filename, atime, mtime); - - const fileInfo = Deno.statSync(filename); - assertEquals(fileInfo.atime, atime); - assertEquals(fileInfo.mtime, mtime); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function utimeNotFound() { - const atime = 1000; - const mtime = 50000; - - await assertRejects( - async () => { - await Deno.utime("/baddir", atime, mtime); - }, - Deno.errors.NotFound, - "utime '/baddir'", - ); - }, -); - -Deno.test( - { permissions: { read: true, write: false } }, - async function utimeSyncPerm() { - const atime = 1000; - const mtime = 50000; - - await assertRejects(async () => { - await Deno.utime("/some_dir", atime, mtime); - }, Deno.errors.PermissionDenied); - }, -); diff --git a/cli/tests/unit/version_test.ts b/cli/tests/unit/version_test.ts deleted file mode 100644 index 4eadb7620..000000000 --- a/cli/tests/unit/version_test.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assert, assertEquals } from "./test_util.ts"; - -Deno.test(function version() { - const pattern = /^\d+\.\d+\.\d+/; - assert(pattern.test(Deno.version.deno)); - assert(pattern.test(Deno.version.v8)); - assertEquals(Deno.version.typescript, "5.3.3"); -}); diff --git a/cli/tests/unit/wasm_test.ts b/cli/tests/unit/wasm_test.ts deleted file mode 100644 index fab9c9308..000000000 --- a/cli/tests/unit/wasm_test.ts +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assert, assertEquals, assertRejects } from "./test_util.ts"; - -// The following blob can be created by taking the following s-expr and pass -// it through wat2wasm. -// (module -// (func $add (param $a i32) (param $b i32) (result i32) -// local.get $a -// local.get $b -// i32.add) -// (export "add" (func $add)) -// ) -// deno-fmt-ignore -const simpleWasm = new Uint8Array([ - 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x60, - 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x07, 0x01, - 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, - 0x00, 0x20, 0x01, 0x6a, 0x0b -]); - -Deno.test(async function wasmInstantiateWorksWithBuffer() { - const { module, instance } = await WebAssembly.instantiate(simpleWasm); - assertEquals(WebAssembly.Module.exports(module), [{ - name: "add", - kind: "function", - }]); - assertEquals(WebAssembly.Module.imports(module), []); - assert(typeof instance.exports.add === "function"); - const add = instance.exports.add as (a: number, b: number) => number; - assertEquals(add(1, 3), 4); -}); - -// V8's default implementation of `WebAssembly.instantiateStreaming()` if you -// don't set the WASM streaming callback, is to take a byte source. Here we -// check that our implementation of the callback disallows it. -Deno.test( - async function wasmInstantiateStreamingFailsWithBuffer() { - await assertRejects(async () => { - await WebAssembly.instantiateStreaming( - // Bypassing the type system - simpleWasm as unknown as Promise<Response>, - ); - }, TypeError); - }, -); - -Deno.test( - async function wasmInstantiateStreamingNoContentType() { - const response = new Response(simpleWasm); - // Rejects, not throws. - const wasmPromise = WebAssembly.instantiateStreaming(response); - await assertRejects( - () => wasmPromise, - TypeError, - "Invalid WebAssembly content type.", - ); - }, -); - -Deno.test(async function wasmInstantiateStreaming() { - let isomorphic = ""; - for (const byte of simpleWasm) { - isomorphic += String.fromCharCode(byte); - } - const base64Url = "data:application/wasm;base64," + btoa(isomorphic); - - const { module, instance } = await WebAssembly.instantiateStreaming( - fetch(base64Url), - ); - assertEquals(WebAssembly.Module.exports(module), [{ - name: "add", - kind: "function", - }]); - assertEquals(WebAssembly.Module.imports(module), []); - assert(typeof instance.exports.add === "function"); - const add = instance.exports.add as (a: number, b: number) => number; - assertEquals(add(1, 3), 4); -}); - -Deno.test( - { permissions: { read: true } }, - async function wasmFileStreaming() { - const url = import.meta.resolve("../testdata/assets/unreachable.wasm"); - assert(url.startsWith("file://")); - - const { module } = await WebAssembly.instantiateStreaming(fetch(url)); - assertEquals(WebAssembly.Module.exports(module), [{ - name: "unreachable", - kind: "function", - }]); - }, -); - -Deno.test( - { permissions: { net: true } }, - async function wasmStreamingNonTrivial() { - // deno-dom's WASM file is a real-world non-trivial case that gave us - // trouble when implementing this. - await WebAssembly.instantiateStreaming(fetch( - "http://localhost:4545/assets/deno_dom_0.1.3-alpha2.wasm", - )); - }, -); diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts deleted file mode 100644 index 829330eba..000000000 --- a/cli/tests/unit/webcrypto_test.ts +++ /dev/null @@ -1,2047 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { - assert, - assertEquals, - assertNotEquals, - assertRejects, -} from "./test_util.ts"; - -// https://github.com/denoland/deno/issues/11664 -Deno.test(async function testImportArrayBufferKey() { - const subtle = window.crypto.subtle; - assert(subtle); - - // deno-fmt-ignore - const key = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); - - const cryptoKey = await subtle.importKey( - "raw", - key.buffer, - { name: "HMAC", hash: "SHA-1" }, - true, - ["sign"], - ); - assert(cryptoKey); - - // Test key usage - await subtle.sign({ name: "HMAC" }, cryptoKey, new Uint8Array(8)); -}); - -Deno.test(async function testSignVerify() { - const subtle = window.crypto.subtle; - assert(subtle); - for (const algorithm of ["RSA-PSS", "RSASSA-PKCS1-v1_5"]) { - for ( - const hash of [ - "SHA-1", - "SHA-256", - "SHA-384", - "SHA-512", - ] - ) { - const keyPair = await subtle.generateKey( - { - name: algorithm, - modulusLength: 2048, - publicExponent: new Uint8Array([1, 0, 1]), - hash, - }, - true, - ["sign", "verify"], - ); - - const data = new Uint8Array([1, 2, 3]); - - const signAlgorithm = { name: algorithm, saltLength: 32 }; - - const signature = await subtle.sign( - signAlgorithm, - keyPair.privateKey, - data, - ); - - assert(signature); - assert(signature.byteLength > 0); - assert(signature.byteLength % 8 == 0); - assert(signature instanceof ArrayBuffer); - - const verified = await subtle.verify( - signAlgorithm, - keyPair.publicKey, - signature, - data, - ); - assert(verified); - } - } -}); - -// deno-fmt-ignore -const plainText = new Uint8Array([95, 77, 186, 79, 50, 12, 12, 232, 118, 114, 90, 252, 229, 251, 210, 91, 248, 62, 90, 113, 37, 160, 140, 175, 231, 60, 62, 186, 196, 33, 119, 157, 249, 213, 93, 24, 12, 58, 233, 148, 38, 69, 225, 216, 47, 238, 140, 157, 41, 75, 60, 177, 160, 138, 153, 49, 32, 27, 60, 14, 129, 252, 71, 202, 207, 131, 21, 162, 175, 102, 50, 65, 19, 195, 182, 98, 48, 195, 70, 8, 196, 244, 89, 54, 52, 206, 2, 178, 103, 54, 34, 119, 240, 168, 64, 202, 116, 188, 61, 26, 98, 54, 149, 44, 94, 215, 170, 248, 168, 254, 203, 221, 250, 117, 132, 230, 151, 140, 234, 93, 42, 91, 159, 183, 241, 180, 140, 139, 11, 229, 138, 48, 82, 2, 117, 77, 131, 118, 16, 115, 116, 121, 60, 240, 38, 170, 238, 83, 0, 114, 125, 131, 108, 215, 30, 113, 179, 69, 221, 178, 228, 68, 70, 255, 197, 185, 1, 99, 84, 19, 137, 13, 145, 14, 163, 128, 152, 74, 144, 25, 16, 49, 50, 63, 22, 219, 204, 157, 107, 225, 104, 184, 72, 133, 56, 76, 160, 62, 18, 96, 10, 193, 194, 72, 2, 138, 243, 114, 108, 201, 52, 99, 136, 46, 168, 192, 42, 171]); - -// Passing -const hashPlainTextVector = [ - { - hash: "SHA-1", - plainText: plainText.slice(0, 214), - }, - { - hash: "SHA-256", - plainText: plainText.slice(0, 190), - }, - { - hash: "SHA-384", - plainText: plainText.slice(0, 158), - }, - { - hash: "SHA-512", - plainText: plainText.slice(0, 126), - }, -]; - -Deno.test(async function testEncryptDecrypt() { - const subtle = window.crypto.subtle; - assert(subtle); - for ( - const { hash, plainText } of hashPlainTextVector - ) { - const keyPair = await subtle.generateKey( - { - name: "RSA-OAEP", - modulusLength: 2048, - publicExponent: new Uint8Array([1, 0, 1]), - hash, - }, - true, - ["encrypt", "decrypt"], - ); - - const encryptAlgorithm = { name: "RSA-OAEP" }; - const cipherText = await subtle.encrypt( - encryptAlgorithm, - keyPair.publicKey, - plainText, - ); - - assert(cipherText); - assert(cipherText.byteLength > 0); - assertEquals(cipherText.byteLength * 8, 2048); - assert(cipherText instanceof ArrayBuffer); - - const decrypted = await subtle.decrypt( - encryptAlgorithm, - keyPair.privateKey, - cipherText, - ); - assert(decrypted); - assert(decrypted instanceof ArrayBuffer); - assertEquals(new Uint8Array(decrypted), plainText); - - const badPlainText = new Uint8Array(plainText.byteLength + 1); - badPlainText.set(plainText, 0); - badPlainText.set(new Uint8Array([32]), plainText.byteLength); - await assertRejects(async () => { - // Should fail - await subtle.encrypt( - encryptAlgorithm, - keyPair.publicKey, - badPlainText, - ); - throw new TypeError("unreachable"); - }, DOMException); - } -}); - -Deno.test(async function testGenerateRSAKey() { - const subtle = window.crypto.subtle; - assert(subtle); - - const keyPair = await subtle.generateKey( - { - name: "RSA-PSS", - modulusLength: 2048, - publicExponent: new Uint8Array([1, 0, 1]), - hash: "SHA-256", - }, - true, - ["sign", "verify"], - ); - - assert(keyPair.privateKey); - assert(keyPair.publicKey); - assertEquals(keyPair.privateKey.extractable, true); - assert(keyPair.privateKey.usages.includes("sign")); -}); - -Deno.test(async function testGenerateHMACKey() { - const key = await window.crypto.subtle.generateKey( - { - name: "HMAC", - hash: "SHA-512", - }, - true, - ["sign", "verify"], - ); - - assert(key); - assertEquals(key.extractable, true); - assert(key.usages.includes("sign")); -}); - -Deno.test(async function testECDSASignVerify() { - const key = await window.crypto.subtle.generateKey( - { - name: "ECDSA", - namedCurve: "P-384", - }, - true, - ["sign", "verify"], - ); - - const encoder = new TextEncoder(); - const encoded = encoder.encode("Hello, World!"); - const signature = await window.crypto.subtle.sign( - { name: "ECDSA", hash: "SHA-384" }, - key.privateKey, - encoded, - ); - - assert(signature); - assert(signature instanceof ArrayBuffer); - - const verified = await window.crypto.subtle.verify( - { hash: { name: "SHA-384" }, name: "ECDSA" }, - key.publicKey, - signature, - encoded, - ); - assert(verified); -}); - -// Tests the "bad paths" as a temporary replacement for sign_verify/ecdsa WPT. -Deno.test(async function testECDSASignVerifyFail() { - const key = await window.crypto.subtle.generateKey( - { - name: "ECDSA", - namedCurve: "P-384", - }, - true, - ["sign", "verify"], - ); - - const encoded = new Uint8Array([1]); - // Signing with a public key (InvalidAccessError) - await assertRejects(async () => { - await window.crypto.subtle.sign( - { name: "ECDSA", hash: "SHA-384" }, - key.publicKey, - new Uint8Array([1]), - ); - throw new TypeError("unreachable"); - }, DOMException); - - // Do a valid sign for later verifying. - const signature = await window.crypto.subtle.sign( - { name: "ECDSA", hash: "SHA-384" }, - key.privateKey, - encoded, - ); - - // Verifying with a private key (InvalidAccessError) - await assertRejects(async () => { - await window.crypto.subtle.verify( - { hash: { name: "SHA-384" }, name: "ECDSA" }, - key.privateKey, - signature, - encoded, - ); - throw new TypeError("unreachable"); - }, DOMException); -}); - -// https://github.com/denoland/deno/issues/11313 -Deno.test(async function testSignRSASSAKey() { - const subtle = window.crypto.subtle; - assert(subtle); - - const keyPair = await subtle.generateKey( - { - name: "RSASSA-PKCS1-v1_5", - modulusLength: 2048, - publicExponent: new Uint8Array([1, 0, 1]), - hash: "SHA-256", - }, - true, - ["sign", "verify"], - ); - - assert(keyPair.privateKey); - assert(keyPair.publicKey); - assertEquals(keyPair.privateKey.extractable, true); - assert(keyPair.privateKey.usages.includes("sign")); - - const encoder = new TextEncoder(); - const encoded = encoder.encode("Hello, World!"); - - const signature = await window.crypto.subtle.sign( - { name: "RSASSA-PKCS1-v1_5" }, - keyPair.privateKey, - encoded, - ); - - assert(signature); -}); - -// deno-fmt-ignore -const rawKey = new Uint8Array([ - 1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15, 16 -]); - -const jwk: JsonWebKey = { - kty: "oct", - // unpadded base64 for rawKey. - k: "AQIDBAUGBwgJCgsMDQ4PEA", - alg: "HS256", - ext: true, - "key_ops": ["sign"], -}; - -Deno.test(async function subtleCryptoHmacImportExport() { - const key1 = await crypto.subtle.importKey( - "raw", - rawKey, - { name: "HMAC", hash: "SHA-256" }, - true, - ["sign"], - ); - const key2 = await crypto.subtle.importKey( - "jwk", - jwk, - { name: "HMAC", hash: "SHA-256" }, - true, - ["sign"], - ); - const actual1 = await crypto.subtle.sign( - { name: "HMAC" }, - key1, - new Uint8Array([1, 2, 3, 4]), - ); - - const actual2 = await crypto.subtle.sign( - { name: "HMAC" }, - key2, - new Uint8Array([1, 2, 3, 4]), - ); - // deno-fmt-ignore - const expected = new Uint8Array([ - 59, 170, 255, 216, 51, 141, 51, 194, - 213, 48, 41, 191, 184, 40, 216, 47, - 130, 165, 203, 26, 163, 43, 38, 71, - 23, 122, 222, 1, 146, 46, 182, 87, - ]); - assertEquals( - new Uint8Array(actual1), - expected, - ); - assertEquals( - new Uint8Array(actual2), - expected, - ); - - const exportedKey1 = await crypto.subtle.exportKey("raw", key1); - assertEquals(new Uint8Array(exportedKey1), rawKey); - - const exportedKey2 = await crypto.subtle.exportKey("jwk", key2); - assertEquals(exportedKey2, jwk); -}); - -// https://github.com/denoland/deno/issues/12085 -Deno.test(async function generateImportHmacJwk() { - const key = await crypto.subtle.generateKey( - { - name: "HMAC", - hash: "SHA-512", - }, - true, - ["sign"], - ); - assert(key); - assertEquals(key.type, "secret"); - assertEquals(key.extractable, true); - assertEquals(key.usages, ["sign"]); - - const exportedKey = await crypto.subtle.exportKey("jwk", key); - assertEquals(exportedKey.kty, "oct"); - assertEquals(exportedKey.alg, "HS512"); - assertEquals(exportedKey.key_ops, ["sign"]); - assertEquals(exportedKey.ext, true); - assert(typeof exportedKey.k == "string"); - assertEquals(exportedKey.k.length, 171); -}); - -// 2048-bits publicExponent=65537 -const pkcs8TestVectors = [ - // rsaEncryption - { pem: "cli/tests/testdata/webcrypto/id_rsaEncryption.pem", hash: "SHA-256" }, -]; - -Deno.test({ permissions: { read: true } }, async function importRsaPkcs8() { - const pemHeader = "-----BEGIN PRIVATE KEY-----"; - const pemFooter = "-----END PRIVATE KEY-----"; - for (const { pem, hash } of pkcs8TestVectors) { - const keyFile = await Deno.readTextFile(pem); - const pemContents = keyFile.substring( - pemHeader.length, - keyFile.length - pemFooter.length, - ); - const binaryDerString = atob(pemContents); - const binaryDer = new Uint8Array(binaryDerString.length); - for (let i = 0; i < binaryDerString.length; i++) { - binaryDer[i] = binaryDerString.charCodeAt(i); - } - - const key = await crypto.subtle.importKey( - "pkcs8", - binaryDer, - { name: "RSA-PSS", hash }, - true, - ["sign"], - ); - - assert(key); - assertEquals(key.type, "private"); - assertEquals(key.extractable, true); - assertEquals(key.usages, ["sign"]); - const algorithm = key.algorithm as RsaHashedKeyAlgorithm; - assertEquals(algorithm.name, "RSA-PSS"); - assertEquals(algorithm.hash.name, hash); - assertEquals(algorithm.modulusLength, 2048); - assertEquals(algorithm.publicExponent, new Uint8Array([1, 0, 1])); - } -}); - -const nonInteroperableVectors = [ - // id-RSASSA-PSS (sha256) - // `openssl genpkey -algorithm rsa-pss -pkeyopt rsa_pss_keygen_md:sha256 -out id_rsassaPss.pem` - { pem: "cli/tests/testdata/webcrypto/id_rsassaPss.pem", hash: "SHA-256" }, - // id-RSASSA-PSS (default parameters) - // `openssl genpkey -algorithm rsa-pss -out id_rsassaPss.pem` - { - pem: "cli/tests/testdata/webcrypto/id_rsassaPss_default.pem", - hash: "SHA-1", - }, - // id-RSASSA-PSS (default hash) - // `openssl genpkey -algorithm rsa-pss -pkeyopt rsa_pss_keygen_saltlen:30 -out rsaPss_saltLen_30.pem` - { - pem: "cli/tests/testdata/webcrypto/id_rsassaPss_saltLen_30.pem", - hash: "SHA-1", - }, -]; - -Deno.test( - { permissions: { read: true } }, - async function importNonInteroperableRsaPkcs8() { - const pemHeader = "-----BEGIN PRIVATE KEY-----"; - const pemFooter = "-----END PRIVATE KEY-----"; - for (const { pem, hash } of nonInteroperableVectors) { - const keyFile = await Deno.readTextFile(pem); - const pemContents = keyFile.substring( - pemHeader.length, - keyFile.length - pemFooter.length, - ); - const binaryDerString = atob(pemContents); - const binaryDer = new Uint8Array(binaryDerString.length); - for (let i = 0; i < binaryDerString.length; i++) { - binaryDer[i] = binaryDerString.charCodeAt(i); - } - - await assertRejects( - () => - crypto.subtle.importKey( - "pkcs8", - binaryDer, - { name: "RSA-PSS", hash }, - true, - ["sign"], - ), - DOMException, - "unsupported algorithm", - ); - } - }, -); - -// deno-fmt-ignore -const asn1AlgorithmIdentifier = new Uint8Array([ - 0x02, 0x01, 0x00, // INTEGER - 0x30, 0x0d, // SEQUENCE (2 elements) - 0x06, 0x09, // OBJECT IDENTIFIER - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, // 1.2.840.113549.1.1.1 (rsaEncryption) - 0x05, 0x00, // NULL -]); - -Deno.test(async function rsaExport() { - for (const algorithm of ["RSASSA-PKCS1-v1_5", "RSA-PSS", "RSA-OAEP"]) { - const keyPair = await crypto.subtle.generateKey( - { - name: algorithm, - modulusLength: 2048, - publicExponent: new Uint8Array([1, 0, 1]), - hash: "SHA-256", - }, - true, - algorithm !== "RSA-OAEP" ? ["sign", "verify"] : ["encrypt", "decrypt"], - ); - - assert(keyPair.privateKey); - assert(keyPair.publicKey); - assertEquals(keyPair.privateKey.extractable, true); - - const exportedPrivateKey = await crypto.subtle.exportKey( - "pkcs8", - keyPair.privateKey, - ); - - assert(exportedPrivateKey); - assert(exportedPrivateKey instanceof ArrayBuffer); - - const pkcs8 = new Uint8Array(exportedPrivateKey); - assert(pkcs8.length > 0); - - assertEquals( - pkcs8.slice(4, asn1AlgorithmIdentifier.byteLength + 4), - asn1AlgorithmIdentifier, - ); - - const exportedPublicKey = await crypto.subtle.exportKey( - "spki", - keyPair.publicKey, - ); - - const spki = new Uint8Array(exportedPublicKey); - assert(spki.length > 0); - - assertEquals( - spki.slice(4, asn1AlgorithmIdentifier.byteLength + 1), - asn1AlgorithmIdentifier.slice(3), - ); - } -}); - -Deno.test(async function testHkdfDeriveBits() { - const rawKey = crypto.getRandomValues(new Uint8Array(16)); - const key = await crypto.subtle.importKey( - "raw", - rawKey, - { name: "HKDF", hash: "SHA-256" }, - false, - ["deriveBits"], - ); - const salt = crypto.getRandomValues(new Uint8Array(16)); - const info = crypto.getRandomValues(new Uint8Array(16)); - const result = await crypto.subtle.deriveBits( - { - name: "HKDF", - hash: "SHA-256", - salt: salt, - info: info, - }, - key, - 128, - ); - assertEquals(result.byteLength, 128 / 8); -}); - -Deno.test(async function testHkdfDeriveBitsWithLargeKeySize() { - const key = await crypto.subtle.importKey( - "raw", - new Uint8Array([0x00]), - "HKDF", - false, - ["deriveBits"], - ); - await assertRejects( - () => - crypto.subtle.deriveBits( - { - name: "HKDF", - hash: "SHA-1", - salt: new Uint8Array(), - info: new Uint8Array(), - }, - key, - ((20 * 255) << 3) + 8, - ), - DOMException, - "The length provided for HKDF is too large", - ); -}); - -Deno.test(async function testEcdhDeriveBitsWithShorterLength() { - const keypair = await crypto.subtle.generateKey( - { - name: "ECDH", - namedCurve: "P-384", - }, - true, - ["deriveBits", "deriveKey"], - ); - const result = await crypto.subtle.deriveBits( - { - name: "ECDH", - public: keypair.publicKey, - }, - keypair.privateKey, - 256, - ); - assertEquals(result.byteLength * 8, 256); -}); - -Deno.test(async function testEcdhDeriveBitsWithLongerLength() { - const keypair = await crypto.subtle.generateKey( - { - name: "ECDH", - namedCurve: "P-384", - }, - true, - ["deriveBits", "deriveKey"], - ); - await assertRejects( - () => - crypto.subtle.deriveBits( - { - name: "ECDH", - public: keypair.publicKey, - }, - keypair.privateKey, - 512, - ), - DOMException, - "Invalid length", - ); -}); - -Deno.test(async function testEcdhDeriveBitsWithNullLength() { - const keypair = await crypto.subtle.generateKey( - { - name: "ECDH", - namedCurve: "P-384", - }, - true, - ["deriveBits", "deriveKey"], - ); - const result = await crypto.subtle.deriveBits( - { - name: "ECDH", - public: keypair.publicKey, - }, - keypair.privateKey, - // @ts-ignore: necessary until .d.ts file allows passing null (see https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1416) - null, - ); - assertEquals(result.byteLength * 8, 384); -}); - -Deno.test(async function testDeriveKey() { - // Test deriveKey - const rawKey = crypto.getRandomValues(new Uint8Array(16)); - const key = await crypto.subtle.importKey( - "raw", - rawKey, - "PBKDF2", - false, - ["deriveKey", "deriveBits"], - ); - - const salt = crypto.getRandomValues(new Uint8Array(16)); - const derivedKey = await crypto.subtle.deriveKey( - { - name: "PBKDF2", - salt, - iterations: 1000, - hash: "SHA-256", - }, - key, - { name: "HMAC", hash: "SHA-256" }, - true, - ["sign"], - ); - - assert(derivedKey instanceof CryptoKey); - assertEquals(derivedKey.type, "secret"); - assertEquals(derivedKey.extractable, true); - assertEquals(derivedKey.usages, ["sign"]); - - const algorithm = derivedKey.algorithm as HmacKeyAlgorithm; - assertEquals(algorithm.name, "HMAC"); - assertEquals(algorithm.hash.name, "SHA-256"); - assertEquals(algorithm.length, 512); -}); - -Deno.test(async function testAesCbcEncryptDecrypt() { - const key = await crypto.subtle.generateKey( - { name: "AES-CBC", length: 128 }, - true, - ["encrypt", "decrypt"], - ); - - const iv = crypto.getRandomValues(new Uint8Array(16)); - const encrypted = await crypto.subtle.encrypt( - { - name: "AES-CBC", - iv, - }, - key as CryptoKey, - new Uint8Array([1, 2, 3, 4, 5, 6]), - ); - - assert(encrypted instanceof ArrayBuffer); - assertEquals(encrypted.byteLength, 16); - - const decrypted = await crypto.subtle.decrypt( - { - name: "AES-CBC", - iv, - }, - key as CryptoKey, - encrypted, - ); - - assert(decrypted instanceof ArrayBuffer); - assertEquals(decrypted.byteLength, 6); - assertEquals(new Uint8Array(decrypted), new Uint8Array([1, 2, 3, 4, 5, 6])); -}); - -Deno.test(async function testAesCtrEncryptDecrypt() { - async function aesCtrRoundTrip( - key: CryptoKey, - counter: Uint8Array, - length: number, - plainText: Uint8Array, - ) { - const cipherText = await crypto.subtle.encrypt( - { - name: "AES-CTR", - counter, - length, - }, - key, - plainText, - ); - - assert(cipherText instanceof ArrayBuffer); - assertEquals(cipherText.byteLength, plainText.byteLength); - assertNotEquals(new Uint8Array(cipherText), plainText); - - const decryptedText = await crypto.subtle.decrypt( - { - name: "AES-CTR", - counter, - length, - }, - key, - cipherText, - ); - - assert(decryptedText instanceof ArrayBuffer); - assertEquals(decryptedText.byteLength, plainText.byteLength); - assertEquals(new Uint8Array(decryptedText), plainText); - } - for (const keySize of [128, 192, 256]) { - const key = await crypto.subtle.generateKey( - { name: "AES-CTR", length: keySize }, - true, - ["encrypt", "decrypt"], - ) as CryptoKey; - - // test normal operation - for (const length of [128 /*, 64, 128 */]) { - const counter = crypto.getRandomValues(new Uint8Array(16)); - - await aesCtrRoundTrip( - key, - counter, - length, - new Uint8Array([1, 2, 3, 4, 5, 6]), - ); - } - - // test counter-wrapping - for (const length of [32, 64, 128]) { - const plaintext1 = crypto.getRandomValues(new Uint8Array(32)); - const counter = new Uint8Array(16); - - // fixed upper part - for (let off = 0; off < 16 - (length / 8); ++off) { - counter[off] = off; - } - const ciphertext1 = await crypto.subtle.encrypt( - { - name: "AES-CTR", - counter, - length, - }, - key, - plaintext1, - ); - - // Set lower [length] counter bits to all '1's - for (let off = 16 - (length / 8); off < 16; ++off) { - counter[off] = 0xff; - } - - // = [ 1 block of 0x00 + plaintext1 ] - const plaintext2 = new Uint8Array(48); - plaintext2.set(plaintext1, 16); - - const ciphertext2 = await crypto.subtle.encrypt( - { - name: "AES-CTR", - counter, - length, - }, - key, - plaintext2, - ); - - // If counter wrapped, 2nd block of ciphertext2 should be equal to 1st block of ciphertext1 - // since ciphertext1 used counter = 0x00...00 - // and ciphertext2 used counter = 0xFF..FF which should wrap to 0x00..00 without affecting - // higher bits - assertEquals( - new Uint8Array(ciphertext1), - new Uint8Array(ciphertext2).slice(16), - ); - } - } -}); - -Deno.test(async function testECDH() { - for (const keySize of [256, 384]) { - const keyPair = await crypto.subtle.generateKey( - { - name: "ECDH", - namedCurve: "P-" + keySize, - }, - true, - ["deriveBits"], - ); - - const derivedKey = await crypto.subtle.deriveBits( - { - name: "ECDH", - public: keyPair.publicKey, - }, - keyPair.privateKey, - keySize, - ); - - assert(derivedKey instanceof ArrayBuffer); - assertEquals(derivedKey.byteLength, keySize / 8); - } -}); - -Deno.test(async function testWrapKey() { - // Test wrapKey - const key = await crypto.subtle.generateKey( - { - name: "RSA-OAEP", - modulusLength: 4096, - publicExponent: new Uint8Array([1, 0, 1]), - hash: "SHA-256", - }, - true, - ["wrapKey", "unwrapKey"], - ); - - const hmacKey = await crypto.subtle.generateKey( - { - name: "HMAC", - hash: "SHA-256", - length: 128, - }, - true, - ["sign"], - ); - - const wrappedKey = await crypto.subtle.wrapKey( - "raw", - hmacKey, - key.publicKey, - { - name: "RSA-OAEP", - label: new Uint8Array(8), - }, - ); - - assert(wrappedKey instanceof ArrayBuffer); - assertEquals(wrappedKey.byteLength, 512); -}); - -// Doesn't need to cover all cases. -// Only for testing types. -Deno.test(async function testAesKeyGen() { - const key = await crypto.subtle.generateKey( - { - name: "AES-GCM", - length: 256, - }, - true, - ["encrypt", "decrypt"], - ); - - assert(key); - assertEquals(key.type, "secret"); - assertEquals(key.extractable, true); - assertEquals(key.usages, ["encrypt", "decrypt"]); - const algorithm = key.algorithm as AesKeyAlgorithm; - assertEquals(algorithm.name, "AES-GCM"); - assertEquals(algorithm.length, 256); -}); - -Deno.test(async function testUnwrapKey() { - const subtle = crypto.subtle; - - const AES_KEY: AesKeyAlgorithm & AesCbcParams = { - name: "AES-CBC", - length: 128, - iv: new Uint8Array(16), - }; - - const RSA_KEY: RsaHashedKeyGenParams & RsaOaepParams = { - name: "RSA-OAEP", - modulusLength: 2048, - publicExponent: new Uint8Array([1, 0, 1]), - hash: "SHA-1", - }; - - const aesKey = await subtle.generateKey(AES_KEY, true, [ - "encrypt", - "decrypt", - ]); - - const rsaKeyPair = await subtle.generateKey( - { - name: "RSA-OAEP", - hash: "SHA-1", - publicExponent: new Uint8Array([1, 0, 1]), - modulusLength: 2048, - }, - false, - ["wrapKey", "encrypt", "unwrapKey", "decrypt"], - ); - - const enc = await subtle.wrapKey( - "raw", - aesKey, - rsaKeyPair.publicKey, - RSA_KEY, - ); - const unwrappedKey = await subtle.unwrapKey( - "raw", - enc, - rsaKeyPair.privateKey, - RSA_KEY, - AES_KEY, - false, - ["encrypt", "decrypt"], - ); - - assert(unwrappedKey instanceof CryptoKey); - assertEquals(unwrappedKey.type, "secret"); - assertEquals(unwrappedKey.extractable, false); - assertEquals(unwrappedKey.usages, ["encrypt", "decrypt"]); -}); - -Deno.test(async function testDecryptWithInvalidIntializationVector() { - // deno-fmt-ignore - const data = new Uint8Array([42,42,42,42,42,42,42,42,42,42,42,42,42,42,42]); - const key = await crypto.subtle.importKey( - "raw", - new Uint8Array(16), - { name: "AES-CBC", length: 256 }, - true, - ["encrypt", "decrypt"], - ); - // deno-fmt-ignore - const initVector = new Uint8Array([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); - const encrypted = await crypto.subtle.encrypt( - { name: "AES-CBC", iv: initVector }, - key, - data, - ); - // deno-fmt-ignore - const initVector2 = new Uint8Array([15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0]); - await assertRejects(async () => { - await crypto.subtle.decrypt( - { name: "AES-CBC", iv: initVector2 }, - key, - encrypted, - ); - }, DOMException); -}); - -const jwtRSAKeys = { - "1024": { - size: 1024, - publicJWK: { - kty: "RSA", - n: "zZn4sRGfjQos56yL_Qy1R9NI-THMnFynn94g5RxA6wGrJh4BJT3x6I9x0IbpS3q-d4ORA6R2vuDMh8dDFRr9RDH6XY-gUScc9U5Jz3UA2KmVfsCbnUPvcAmMV_ENA7_TF0ivVjuIFodyDTx7EKHNVTrHHSlrbt7spbmcivs23Zc", - e: "AQAB", - }, - privateJWK: { - kty: "RSA", - n: "zZn4sRGfjQos56yL_Qy1R9NI-THMnFynn94g5RxA6wGrJh4BJT3x6I9x0IbpS3q-d4ORA6R2vuDMh8dDFRr9RDH6XY-gUScc9U5Jz3UA2KmVfsCbnUPvcAmMV_ENA7_TF0ivVjuIFodyDTx7EKHNVTrHHSlrbt7spbmcivs23Zc", - e: "AQAB", - d: "YqIK_GdH85F-GWZdgfgmv15NE78gOaL5h2g4v7DeM9-JC7A5PHSLKNYn87HFGcC4vv0PBIBRtyCA_mJJfEaGWORVCOXSBpWNepMYpio52n3w5uj5UZEsBnbtZc0EtWhVF2Auqa7VbiKrWcQUEgEI8V0gE5D4tyBg8GXv9975dQE", - p: "9BrAg5L1zfqGPuWJDuDCBX-TmtZdrOI3Ys4ZaN-yMPlTjwWSEPO0qnfjEZcw2VgXHgJJmbVco6TxckJCmEYqeQ", - q: "157jDJ1Ya5nmQvTPbhKAPAeMWogxCyaQTkBrp30pEKd6mGSB385hqr4BIk8s3f7MdXpM-USpaZgUoT4o_2VEjw", - dp: - "qdd_QUzcaB-6jkKo1Ug-1xKIAgDLFsIjJUUfWt_iHL8ti2Kl2dOnTcCypgebPm5TT1bqHN-agGYAdK5zpX2UiQ", - dq: - "hNRfwOSplNfhLvxLUN7a2qA3yYm-1MSz_1DWQP7srlLORlUcYPht2FZmsnEeDcAqynBGPQUcbG2Av_hgHz2OZw", - qi: - "zbpJQAhinrxSbVKxBQ2EZGFUD2e3WCXbAJRYpk8HVQ5AA52OhKTicOye2hEHnrgpFKzC8iznTsCG3FMkvwcj4Q", - }, - }, - - "2048": { - size: 2048, - publicJWK: { - kty: "RSA", - // unpadded base64 for rawKey. - n: "09eVwAhT9SPBxdEN-74BBeEANGaVGwqH-YglIc4VV7jfhR2by5ivzVq8NCeQ1_ACDIlTDY8CTMQ5E1c1SEXmo_T7q84XUGXf8U9mx6uRg46sV7fF-hkwJR80BFVsvWxp4ahPlVJYj__94ft7rIVvchb5tyalOjrYFCJoFnSgq-i3ZjU06csI9XnO5klINucD_Qq0vUhO23_Add2HSYoRjab8YiJJR_Eths7Pq6HHd2RSXmwYp5foRnwe0_U75XmesHWDJlJUHYbwCZo0kP9G8g4QbucwU-MSNBkZOO2x2ZtZNexpHd0ThkATbnNlpVG_z2AGNORp_Ve3rlXwrGIXXw", - e: "AQAB", - }, - privateJWK: { - kty: "RSA", - // unpadded base64 for rawKey. - n: "09eVwAhT9SPBxdEN-74BBeEANGaVGwqH-YglIc4VV7jfhR2by5ivzVq8NCeQ1_ACDIlTDY8CTMQ5E1c1SEXmo_T7q84XUGXf8U9mx6uRg46sV7fF-hkwJR80BFVsvWxp4ahPlVJYj__94ft7rIVvchb5tyalOjrYFCJoFnSgq-i3ZjU06csI9XnO5klINucD_Qq0vUhO23_Add2HSYoRjab8YiJJR_Eths7Pq6HHd2RSXmwYp5foRnwe0_U75XmesHWDJlJUHYbwCZo0kP9G8g4QbucwU-MSNBkZOO2x2ZtZNexpHd0ThkATbnNlpVG_z2AGNORp_Ve3rlXwrGIXXw", - e: "AQAB", - d: "H4xboN2co0VP9kXL71G8lUOM5EDis8Q9u8uqu_4U75t4rjpamVeD1vFMVfgOehokM_m_hKVnkkcmuNqj9L90ObaiRFPM5QxG7YkFpXbHlPAKeoXD1hsqMF0VQg_2wb8DhberInHA_rEA_kaVhHvavQLu7Xez45gf1d_J4I4931vjlCB6cupbLL0H5hHsxbMsX_5nnmAJdL_U3gD-U7ZdQheUPhDBJR2KeGzvnTm3KVKpOnwn-1Cd45MU4-KDdP0FcBVEuBsSrsQHliTaciBgkbyj__BangPj3edDxTkb-fKkEvhkXRjAoJs1ixt8nfSGDce9cM_GqAX9XGb4s2QkAQ", - dp: - "mM82RBwzGzi9LAqjGbi-badLtHRRBoH9sfMrJuOtzxRnmwBFccg_lwy-qAhUTqnN9kvD0H1FzXWzoFPFJbyi-AOmumYGpWm_PvzQGldne5CPJ02pYaeg-t1BePsT3OpIq0Am8E2Kjf9polpRJwIjO7Kx8UJKkhg5bISnsy0V8wE", - dq: - "ZlM4AvrWIpXwqsH_5Q-6BsLJdbnN_GypFCXoT9VXniXncSBZIWCkgDndBdWkSzyzIN65NiMRBfZaf9yduTFj4kvOPwb3ch3J0OxGJk0Ary4OGSlS1zNwMl93ALGal1FzpWUuiia9L9RraGqXAUr13L7TIIMRobRjpAV-z7M-ruM", - p: "7VwGt_tJcAFQHrmDw5dM1EBru6fidM45NDv6VVOEbxKuD5Sh2EfAHfm5c6oouA1gZqwvKH0sn_XpB1NsyYyHEQd3sBVdK0zRjTo-E9mRP-1s-LMd5YDXVq6HE339nxpXsmO25slQEF6zBrj1bSNNXBFc7fgDnlq-HIeleMvsY_E", - q: "5HqMHLzb4IgXhUl4pLz7E4kjY8PH2YGzaQfK805zJMbOXzmlZK0hizKo34Qqd2nB9xos7QgzOYQrNfSWheARwVsSQzAE0vGvw3zHIPP_lTtChBlCTPctQcURjw4dXcnK1oQ-IT321FNOW3EO-YTsyGcypJqJujlZrLbxYjOjQE8", - qi: - "OQXzi9gypDnpdHatIi0FaUGP8LSzfVH0AUugURJXs4BTJpvA9y4hcpBQLrcl7H_vq6kbGmvC49V-9I5HNVX_AuxGIXKuLZr5WOxPq8gLTqHV7X5ZJDtWIP_nq2NNgCQQyNNRrxebiWlwGK9GnX_unewT6jopI_oFhwp0Q13rBR0", - }, - }, - "4096": { - size: 4096, - publicJWK: { - kty: "RSA", - n: "2qr2TL2c2JmbsN0OLIRnaAB_ZKb1-Gh9H0qb4lrBuDaqkW_eFPwT-JIsvnNJvDT7BLJ57tTMIj56ZMtv6efSSTWSk9MOoW2J1K_iEretZ2cegB_aRX7qQVjnoFsz9U02BKfAIUT0o_K7b9G08d1rrAUohi_SVQhwObodg7BddMbKUmz70QNIS487LN44WUVnn9OgE9atTYUARNukT0DuQb3J-K20ksTuVujXbSelohDmLobqlGoi5sY_548Qs9BtFmQ2nGuEHNB2zdlZ5EvEqbUFVZ2QboG6jXdoos6qcwdgUvAhj1Hz10Ngic_RFqL7bNDoIOzNp66hdA35uxbwuaygZ16ikxoPj7eTYud1hrkyQCgeGw2YhCiKIE6eos_U5dL7WHRD5aSkkzsgXtnF8pVmStsuf0QcdAoC-eeCex0tSTgRw9AtGTz8Yr1tGQD9l_580zAXnE6jmrwRRQ68EEA7vohGov3tnG8pGyg_zcxeADLtPlfTc1tEwmh3SGrioDClioYCipm1JvkweEgP9eMPpEC8SgRU1VNDSVe1SF4uNsH8vA7PHFKfg6juqJEc5ht-l10FYER-Qq6bZXsU2oNcfE5SLDeLTWmxiHmxK00M8ABMFIV5gUkPoMiWcl87O6XwzA2chsIERp7Vb-Vn2O-EELiXzv7lPhc6fTGQ0Nc", - e: "AQAB", - }, - privateJWK: { - kty: "RSA", - n: "2qr2TL2c2JmbsN0OLIRnaAB_ZKb1-Gh9H0qb4lrBuDaqkW_eFPwT-JIsvnNJvDT7BLJ57tTMIj56ZMtv6efSSTWSk9MOoW2J1K_iEretZ2cegB_aRX7qQVjnoFsz9U02BKfAIUT0o_K7b9G08d1rrAUohi_SVQhwObodg7BddMbKUmz70QNIS487LN44WUVnn9OgE9atTYUARNukT0DuQb3J-K20ksTuVujXbSelohDmLobqlGoi5sY_548Qs9BtFmQ2nGuEHNB2zdlZ5EvEqbUFVZ2QboG6jXdoos6qcwdgUvAhj1Hz10Ngic_RFqL7bNDoIOzNp66hdA35uxbwuaygZ16ikxoPj7eTYud1hrkyQCgeGw2YhCiKIE6eos_U5dL7WHRD5aSkkzsgXtnF8pVmStsuf0QcdAoC-eeCex0tSTgRw9AtGTz8Yr1tGQD9l_580zAXnE6jmrwRRQ68EEA7vohGov3tnG8pGyg_zcxeADLtPlfTc1tEwmh3SGrioDClioYCipm1JvkweEgP9eMPpEC8SgRU1VNDSVe1SF4uNsH8vA7PHFKfg6juqJEc5ht-l10FYER-Qq6bZXsU2oNcfE5SLDeLTWmxiHmxK00M8ABMFIV5gUkPoMiWcl87O6XwzA2chsIERp7Vb-Vn2O-EELiXzv7lPhc6fTGQ0Nc", - e: "AQAB", - d: "uXPRXBhcE5-DWabBRKQuhxgU8ype5gTISWefeYP7U96ZHqu_sBByZ5ihdgyU9pgAZGVx4Ep9rnVKnH2lNr2zrP9Qhyqy99nM0aMxmypIWLAuP__DwLj4t99M4sU29c48CAq1egHfccSFjzpNuetOTCA71EJuokt70pm0OmGzgTyvjuR7VTLxd5PMXitBowSn8_cphmnFpT8tkTiuy8CH0R3DU7MOuINomDD1s8-yPBcVAVTPUnwJiauNuzestLQKMLlhT5wn-cAbYk36XRKdgkjSc2AkhHRl4WDqT1nzWYdh_DVIYSLiKSktkPO9ovMrRYiPtozfhl0m9SR9Ll0wXtcnnDlWXc_MSGpw18vmUBSJ4PIhkiFsvLn-db3wUkA8uve-iqqfk0sxlGWughWx03kGmZDmprWbXugCBHfsI4X93w4exznXH_tapxPnmjbhVUQR6p41MvO2lcHWPLwGJgLIoejBHpnn3TmMN0UjFZki7q9B_dJ3fXh0mX9DzAlC0sil1NgCPhMPq02393_giinQquMknrBvgKxGSfGUrDKuflCx611ZZlRM3R7YMX2OIy1g4DyhPzBVjxRMtm8PnIs3m3Hi-O-C_PHF93w9J8Wqd0yIw7SpavDqZXLPC6Cqi8K7MBZyVECXHtRj1bBqT-h_xZmFCDjSU0NqfOdgApE", - p: "9NrXwq4kY9kBBOwLoFZVQc4kJI_NbKa_W9FLdQdRIbMsZZHXJ3XDUR9vJAcaaR75WwIC7X6N55nVtWTq28Bys9flJ9RrCTfciOntHEphBhYaL5ZTUl-6khYmsOf_psff2VaOOCvHGff5ejuOmBQxkw2E-cv7knRgWFHoLWpku2NJIMuGHt9ks7OAUfIZVYl9YJnw4FYUzhgaxemknjLeZ8XTkGW2zckzF-d95YI9i8zD80Umubsw-YxriSfqFQ0rGHBsbQ8ZOTd_KJju42BWnXIjNDYmjFUqdzVjI4XQ8EGrCEf_8_iwphGyXD7LOJ4fqd97B3bYpoRTPnCgY_SEHQ", - q: "5J758_NeKr1XPZiLxXohYQQnh0Lb4QtGZ1xzCgjhBQLcIBeTOG_tYjCues9tmLt93LpJfypSJ-SjDLwkR2s069_IByYGpxyeGtV-ulqYhSw1nD2CXKMDGyO5jXDs9tJrS_UhfobXKQH03CRdFugyPkSNmXY-AafFynG7xLr7oYBC05FnhUXPm3VBTPt9K-BpqwYd_h9vkAWeprSPo83UlwcLMupSJY9LaHxhRdz2yi0ZKNwXXHRwcszGjDBvvzUcCYbqWqjzbEvFY6KtH8Jh4LhM46rHaoEOTernJsDF6a6W8Df88RthqTExcwnaQf0O_dlbjSxEIPfbxx8t1EQugw", - dp: - "4Y7Hu5tYAnLhMXuQqj9dgqU3PkcKYdCp7xc6f7Ah2P2JJHfYz4z4RD7Ez1eLyNKzulZ8A_PVHUjlSZiRkaYTBAEaJDrV70P6cFWuC6WpA0ZREQ1V7EgrQnANbGILa8QsPbYyhSQu4YlB1IwQq5_OmzyVBtgWA7AZIMMzMsMT0FuB_if-gWohBjmRN-vh0p45VUf6UW568-_YmgDFmMYbg1UFs7s_TwrNenPR0h7MO4CB8hP9vJLoZrooRczzIjljPbwy5bRG9CJfjTJ0vhj9MUT3kR1hHV1HJVGU5iBbfTfBKnvJGSI6-IDM4ZUm-B0R5hbs6s9cfOjhFmACIJIbMQ", - dq: - "gT4iPbfyHyVEwWyQb4X4grjvg7bXSKSwG1SXMDAOzV9tg7LwJjKYNy8gJAtJgNNVdsfVLs-E_Epzpoph1AIWO9YZZXkov6Yc9zyEVONMX9S7ReU74hTBd8E9b2lMfMg9ogYk9jtSPTt-6kigW4fOh4cHqZ6_tP3cgfLD3JZ8FDPHE4WaySvLDq49yUBO5dQKyIU_xV6OGhQjOUjP_yEoMmzn9tOittsIHTxbXTxqQ6c1FvU9O6YTv8Jl5_Cl66khfX1I1RG38xvurcHULyUbYgeuZ_Iuo9XreT73h9_owo9RguGT29XH4vcNZmRGf5GIvRb4e5lvtleIZkwJA3u78w", - qi: - "JHmVKb1zwW5iRR6RCeexYnh2fmY-3DrPSdM8Dxhr0F8dayi-tlRqEdnG0hvp45n8gLUskWWcB9EXlUJObZGKDfGuxgMa3g_xeLA2vmFQ12MxPsyH4iCNZvsgmGxx7TuOHrnDh5EBVnM4_de63crEJON2sYI8Ozi-xp2OEmAr2seWKq4sxkFni6exLhqb-NE4m9HMKlng1EtQh2rLBFG1VYD3SYYpMLc5fxzqGvSxn3Fa-Xgg-IZPY3ubrcm52KYgmLUGmnYStfVqGSWSdhDXHlNgI5pdAA0FzpyBk3ZX-JsxhwcnneKrYBBweq06kRMGWgvdbdAQ-7wSeGqqj5VPwA", - }, - }, -}; - -Deno.test(async function testImportRsaJwk() { - const subtle = window.crypto.subtle; - assert(subtle); - - for (const [_key, jwkData] of Object.entries(jwtRSAKeys)) { - const { size, publicJWK, privateJWK } = jwkData; - if (size < 2048) { - continue; - } - - // 1. Test import PSS - for (const hash of ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]) { - const hashMapPSS: Record<string, string> = { - "SHA-1": "PS1", - "SHA-256": "PS256", - "SHA-384": "PS384", - "SHA-512": "PS512", - }; - - if (size == 1024 && hash == "SHA-512") { - continue; - } - - const privateKeyPSS = await crypto.subtle.importKey( - "jwk", - { - alg: hashMapPSS[hash], - ...privateJWK, - ext: true, - "key_ops": ["sign"], - }, - { name: "RSA-PSS", hash }, - true, - ["sign"], - ); - - const publicKeyPSS = await crypto.subtle.importKey( - "jwk", - { - alg: hashMapPSS[hash], - ...publicJWK, - ext: true, - "key_ops": ["verify"], - }, - { name: "RSA-PSS", hash }, - true, - ["verify"], - ); - - const signaturePSS = await crypto.subtle.sign( - { name: "RSA-PSS", saltLength: 32 }, - privateKeyPSS, - new Uint8Array([1, 2, 3, 4]), - ); - - const verifyPSS = await crypto.subtle.verify( - { name: "RSA-PSS", saltLength: 32 }, - publicKeyPSS, - signaturePSS, - new Uint8Array([1, 2, 3, 4]), - ); - assert(verifyPSS); - } - - // 2. Test import PKCS1 - for (const hash of ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]) { - const hashMapPKCS1: Record<string, string> = { - "SHA-1": "RS1", - "SHA-256": "RS256", - "SHA-384": "RS384", - "SHA-512": "RS512", - }; - - if (size == 1024 && hash == "SHA-512") { - continue; - } - - const privateKeyPKCS1 = await crypto.subtle.importKey( - "jwk", - { - alg: hashMapPKCS1[hash], - ...privateJWK, - ext: true, - "key_ops": ["sign"], - }, - { name: "RSASSA-PKCS1-v1_5", hash }, - true, - ["sign"], - ); - - const publicKeyPKCS1 = await crypto.subtle.importKey( - "jwk", - { - alg: hashMapPKCS1[hash], - ...publicJWK, - ext: true, - "key_ops": ["verify"], - }, - { name: "RSASSA-PKCS1-v1_5", hash }, - true, - ["verify"], - ); - - const signaturePKCS1 = await crypto.subtle.sign( - { name: "RSASSA-PKCS1-v1_5", saltLength: 32 }, - privateKeyPKCS1, - new Uint8Array([1, 2, 3, 4]), - ); - - const verifyPKCS1 = await crypto.subtle.verify( - { name: "RSASSA-PKCS1-v1_5", saltLength: 32 }, - publicKeyPKCS1, - signaturePKCS1, - new Uint8Array([1, 2, 3, 4]), - ); - assert(verifyPKCS1); - } - - // 3. Test import OAEP - for ( - const { hash, plainText } of hashPlainTextVector - ) { - const hashMapOAEP: Record<string, string> = { - "SHA-1": "RSA-OAEP", - "SHA-256": "RSA-OAEP-256", - "SHA-384": "RSA-OAEP-384", - "SHA-512": "RSA-OAEP-512", - }; - - if (size == 1024 && hash == "SHA-512") { - continue; - } - - const encryptAlgorithm = { name: "RSA-OAEP" }; - - const privateKeyOAEP = await crypto.subtle.importKey( - "jwk", - { - alg: hashMapOAEP[hash], - ...privateJWK, - ext: true, - "key_ops": ["decrypt"], - }, - { ...encryptAlgorithm, hash }, - true, - ["decrypt"], - ); - - const publicKeyOAEP = await crypto.subtle.importKey( - "jwk", - { - alg: hashMapOAEP[hash], - ...publicJWK, - ext: true, - "key_ops": ["encrypt"], - }, - { ...encryptAlgorithm, hash }, - true, - ["encrypt"], - ); - const cipherText = await subtle.encrypt( - encryptAlgorithm, - publicKeyOAEP, - plainText, - ); - - assert(cipherText); - assert(cipherText.byteLength > 0); - assertEquals(cipherText.byteLength * 8, size); - assert(cipherText instanceof ArrayBuffer); - - const decrypted = await subtle.decrypt( - encryptAlgorithm, - privateKeyOAEP, - cipherText, - ); - assert(decrypted); - assert(decrypted instanceof ArrayBuffer); - assertEquals(new Uint8Array(decrypted), plainText); - } - } -}); - -const jwtECKeys = { - "256": { - size: 256, - algo: "ES256", - publicJWK: { - kty: "EC", - crv: "P-256", - x: "0hCwpvnZ8BKGgFi0P6T0cQGFQ7ugDJJQ35JXwqyuXdE", - y: "zgN1UtSBRQzjm00QlXAbF1v6s0uObAmeGPHBmDWDYeg", - }, - privateJWK: { - kty: "EC", - crv: "P-256", - x: "0hCwpvnZ8BKGgFi0P6T0cQGFQ7ugDJJQ35JXwqyuXdE", - y: "zgN1UtSBRQzjm00QlXAbF1v6s0uObAmeGPHBmDWDYeg", - d: "E9M6LVq_nPnrsh_4YNSu_m5W53eQ9N7ptAiE69M1ROo", - }, - }, - "384": { - size: 384, - algo: "ES384", - publicJWK: { - kty: "EC", - crv: "P-384", - x: "IZwU1mYXs27G2IVrOFtzp000T9iude8EZDXdpU47RL1fvevR0I3Wni19wdwhjLQ1", - y: "vSgTjMd4M3qEL2vWGyQOdCSfJGZ8KlgQp2v8KOAzX4imUB3sAZdtqFr7AIactqzo", - }, - privateJWK: { - kty: "EC", - crv: "P-384", - x: "IZwU1mYXs27G2IVrOFtzp000T9iude8EZDXdpU47RL1fvevR0I3Wni19wdwhjLQ1", - y: "vSgTjMd4M3qEL2vWGyQOdCSfJGZ8KlgQp2v8KOAzX4imUB3sAZdtqFr7AIactqzo", - d: "RTe1mQeE08LSLpao-S-hqkku6HPldqQVguFEGDyYiNEOa560ztSyzEAS5KxeqEBz", - }, - }, -}; - -type JWK = Record<string, string>; - -function equalJwk(expected: JWK, got: JWK): boolean { - const fields = Object.keys(expected); - - for (let i = 0; i < fields.length; i++) { - const fieldName = fields[i]; - - if (!(fieldName in got)) { - return false; - } - if (expected[fieldName] !== got[fieldName]) { - return false; - } - } - - return true; -} - -Deno.test(async function testImportExportEcDsaJwk() { - const subtle = crypto.subtle; - assert(subtle); - - for ( - const [_key, keyData] of Object.entries(jwtECKeys) - ) { - const { publicJWK, privateJWK, algo } = keyData; - - // 1. Test import EcDsa - const privateKeyECDSA = await subtle.importKey( - "jwk", - { - alg: algo, - ...privateJWK, - ext: true, - "key_ops": ["sign"], - }, - { name: "ECDSA", namedCurve: privateJWK.crv }, - true, - ["sign"], - ); - const expPrivateKeyJWK = await subtle.exportKey( - "jwk", - privateKeyECDSA, - ); - assert(equalJwk(privateJWK, expPrivateKeyJWK as JWK)); - - const publicKeyECDSA = await subtle.importKey( - "jwk", - { - alg: algo, - ...publicJWK, - ext: true, - "key_ops": ["verify"], - }, - { name: "ECDSA", namedCurve: publicJWK.crv }, - true, - ["verify"], - ); - - const expPublicKeyJWK = await subtle.exportKey( - "jwk", - publicKeyECDSA, - ); - - assert(equalJwk(publicJWK, expPublicKeyJWK as JWK)); - - const signatureECDSA = await subtle.sign( - { name: "ECDSA", hash: `SHA-${keyData.size}` }, - privateKeyECDSA, - new Uint8Array([1, 2, 3, 4]), - ); - - const verifyECDSA = await subtle.verify( - { name: "ECDSA", hash: `SHA-${keyData.size}` }, - publicKeyECDSA, - signatureECDSA, - new Uint8Array([1, 2, 3, 4]), - ); - assert(verifyECDSA); - } -}); - -Deno.test(async function testImportEcDhJwk() { - const subtle = crypto.subtle; - assert(subtle); - - for ( - const [_key, jwkData] of Object.entries(jwtECKeys) - ) { - const { size, publicJWK, privateJWK } = jwkData; - - // 1. Test import EcDsa - const privateKeyECDH = await subtle.importKey( - "jwk", - { - ...privateJWK, - ext: true, - "key_ops": ["deriveBits"], - }, - { name: "ECDH", namedCurve: privateJWK.crv }, - true, - ["deriveBits"], - ); - - const expPrivateKeyJWK = await subtle.exportKey( - "jwk", - privateKeyECDH, - ); - assert(equalJwk(privateJWK, expPrivateKeyJWK as JWK)); - - const publicKeyECDH = await subtle.importKey( - "jwk", - { - ...publicJWK, - ext: true, - "key_ops": [], - }, - { name: "ECDH", namedCurve: publicJWK.crv }, - true, - [], - ); - const expPublicKeyJWK = await subtle.exportKey( - "jwk", - publicKeyECDH, - ); - assert(equalJwk(publicJWK, expPublicKeyJWK as JWK)); - - const derivedKey = await subtle.deriveBits( - { - name: "ECDH", - public: publicKeyECDH, - }, - privateKeyECDH, - size, - ); - - assert(derivedKey instanceof ArrayBuffer); - assertEquals(derivedKey.byteLength, size / 8); - } -}); - -const ecTestKeys = [ - { - size: 256, - namedCurve: "P-256", - signatureLength: 64, - // deno-fmt-ignore - raw: new Uint8Array([ - 4, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128, 88, 180, 63, 164, 244, - 113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146, 87, 194, 172, 174, 93, - 209, 206, 3, 117, 82, 212, 129, 69, 12, 227, 155, 77, 16, 149, 112, 27, - 23, 91, 250, 179, 75, 142, 108, 9, 158, 24, 241, 193, 152, 53, 131, 97, - 232, - ]), - // deno-fmt-ignore - spki: new Uint8Array([ - 48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, - 61, 3, 1, 7, 3, 66, 0, 4, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128, - 88, 180, 63, 164, 244, 113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146, - 87, 194, 172, 174, 93, 209, 206, 3, 117, 82, 212, 129, 69, 12, 227, 155, - 77, 16, 149, 112, 27, 23, 91, 250, 179, 75, 142, 108, 9, 158, 24, 241, - 193, 152, 53, 131, 97, 232, - ]), - // deno-fmt-ignore - pkcs8: new Uint8Array([ - 48, 129, 135, 2, 1, 0, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, - 134, 72, 206, 61, 3, 1, 7, 4, 109, 48, 107, 2, 1, 1, 4, 32, 19, 211, 58, - 45, 90, 191, 156, 249, 235, 178, 31, 248, 96, 212, 174, 254, 110, 86, 231, - 119, 144, 244, 222, 233, 180, 8, 132, 235, 211, 53, 68, 234, 161, 68, 3, - 66, 0, 4, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128, 88, 180, 63, - 164, 244, 113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146, 87, 194, 172, - 174, 93, 209, 206, 3, 117, 82, 212, 129, 69, 12, 227, 155, 77, 16, 149, - 112, 27, 23, 91, 250, 179, 75, 142, 108, 9, 158, 24, 241, 193, 152, 53, - 131, 97, 232, - ]), - }, - { - size: 384, - namedCurve: "P-384", - signatureLength: 96, - // deno-fmt-ignore - raw: new Uint8Array([ - 4, 118, 64, 176, 165, 100, 177, 112, 49, 254, 58, 53, 158, 63, 73, 200, - 148, 248, 242, 216, 186, 80, 92, 160, 53, 64, 232, 157, 19, 1, 12, 226, - 115, 51, 42, 143, 98, 206, 55, 220, 108, 78, 24, 71, 157, 21, 120, 126, - 104, 157, 86, 48, 226, 110, 96, 52, 48, 77, 170, 9, 231, 159, 26, 165, - 200, 26, 164, 99, 46, 227, 169, 105, 172, 225, 60, 102, 141, 145, 139, - 165, 47, 72, 53, 17, 17, 246, 161, 220, 26, 21, 23, 219, 1, 107, 185, - 163, 215, - ]), - // deno-fmt-ignore - spki: new Uint8Array([ - 48, 118, 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 5, 43, 129, 4, 0, - 34, 3, 98, 0, 4, 118, 64, 176, 165, 100, 177, 112, 49, 254, 58, 53, 158, - 63, 73, 200, 148, 248, 242, 216, 186, 80, 92, 160, 53, 64, 232, 157, 19, - 1, 12, 226, 115, 51, 42, 143, 98, 206, 55, 220, 108, 78, 24, 71, 157, 21, - 120, 126, 104, 157, 86, 48, 226, 110, 96, 52, 48, 77, 170, 9, 231, 159, - 26, 165, 200, 26, 164, 99, 46, 227, 169, 105, 172, 225, 60, 102, 141, - 145, 139, 165, 47, 72, 53, 17, 17, 246, 161, 220, 26, 21, 23, 219, 1, - 107, 185, 163, 215, - ]), - // deno-fmt-ignore - pkcs8: new Uint8Array([ - 48, 129, 182, 2, 1, 0, 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 5, 43, - 129, 4, 0, 34, 4, 129, 158, 48, 129, 155, 2, 1, 1, 4, 48, 202, 7, 195, - 169, 124, 170, 81, 169, 253, 127, 56, 28, 98, 90, 255, 165, 72, 142, 133, - 138, 237, 200, 176, 92, 179, 192, 83, 28, 47, 118, 157, 152, 47, 65, 133, - 140, 50, 83, 182, 191, 224, 96, 216, 179, 59, 150, 15, 233, 161, 100, 3, - 98, 0, 4, 118, 64, 176, 165, 100, 177, 112, 49, 254, 58, 53, 158, 63, 73, - 200, 148, 248, 242, 216, 186, 80, 92, 160, 53, 64, 232, 157, 19, 1, 12, - 226, 115, 51, 42, 143, 98, 206, 55, 220, 108, 78, 24, 71, 157, 21, 120, - 126, 104, 157, 86, 48, 226, 110, 96, 52, 48, 77, 170, 9, 231, 159, 26, - 165, 200, 26, 164, 99, 46, 227, 169, 105, 172, 225, 60, 102, 141, 145, - 139, 165, 47, 72, 53, 17, 17, 246, 161, 220, 26, 21, 23, 219, 1, 107, - 185, 163, 215, - ]), - }, -]; - -Deno.test(async function testImportEcSpkiPkcs8() { - const subtle = window.crypto.subtle; - assert(subtle); - - for ( - const { namedCurve, raw, spki, pkcs8, signatureLength } of ecTestKeys - ) { - const rawPublicKeyECDSA = await subtle.importKey( - "raw", - raw, - { name: "ECDSA", namedCurve }, - true, - ["verify"], - ); - - const expPublicKeyRaw = await subtle.exportKey( - "raw", - rawPublicKeyECDSA, - ); - - assertEquals(new Uint8Array(expPublicKeyRaw), raw); - - const privateKeyECDSA = await subtle.importKey( - "pkcs8", - pkcs8, - { name: "ECDSA", namedCurve }, - true, - ["sign"], - ); - - const expPrivateKeyPKCS8 = await subtle.exportKey( - "pkcs8", - privateKeyECDSA, - ); - - assertEquals(new Uint8Array(expPrivateKeyPKCS8), pkcs8); - - const expPrivateKeyJWK = await subtle.exportKey( - "jwk", - privateKeyECDSA, - ); - - assertEquals(expPrivateKeyJWK.crv, namedCurve); - - const publicKeyECDSA = await subtle.importKey( - "spki", - spki, - { name: "ECDSA", namedCurve }, - true, - ["verify"], - ); - - const expPublicKeySPKI = await subtle.exportKey( - "spki", - publicKeyECDSA, - ); - - assertEquals(new Uint8Array(expPublicKeySPKI), spki); - - const expPublicKeyJWK = await subtle.exportKey( - "jwk", - publicKeyECDSA, - ); - - assertEquals(expPublicKeyJWK.crv, namedCurve); - - for ( - const hash of ["SHA-1", "SHA-256", "SHA-384", "SHA-512"] - ) { - if ( - (hash == "SHA-256" && namedCurve == "P-256") || - (hash == "SHA-384" && namedCurve == "P-384") - ) { - const signatureECDSA = await subtle.sign( - { name: "ECDSA", hash }, - privateKeyECDSA, - new Uint8Array([1, 2, 3, 4]), - ); - - const verifyECDSA = await subtle.verify( - { name: "ECDSA", hash }, - publicKeyECDSA, - signatureECDSA, - new Uint8Array([1, 2, 3, 4]), - ); - assert(verifyECDSA); - } else { - await assertRejects( - async () => { - await subtle.sign( - { name: "ECDSA", hash }, - privateKeyECDSA, - new Uint8Array([1, 2, 3, 4]), - ); - }, - DOMException, - "Not implemented", - ); - await assertRejects( - async () => { - await subtle.verify( - { name: "ECDSA", hash }, - publicKeyECDSA, - new Uint8Array(signatureLength), - new Uint8Array([1, 2, 3, 4]), - ); - }, - DOMException, - "Not implemented", - ); - } - } - } -}); - -Deno.test(async function testAesGcmEncrypt() { - const key = await crypto.subtle.importKey( - "raw", - new Uint8Array(16), - { name: "AES-GCM", length: 256 }, - true, - ["encrypt", "decrypt"], - ); - - const nonces = [{ - iv: new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]), - ciphertext: new Uint8Array([ - 50, - 223, - 112, - 178, - 166, - 156, - 255, - 110, - 125, - 138, - 95, - 141, - 82, - 47, - 14, - 164, - 134, - 247, - 22, - ]), - }, { - iv: new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]), - ciphertext: new Uint8Array([ - 210, - 101, - 81, - 216, - 151, - 9, - 192, - 197, - 62, - 254, - 28, - 132, - 89, - 106, - 40, - 29, - 175, - 232, - 201, - ]), - }]; - for (const { iv, ciphertext: fixture } of nonces) { - const data = new Uint8Array([1, 2, 3]); - - const cipherText = await crypto.subtle.encrypt( - { name: "AES-GCM", iv }, - key, - data, - ); - - assert(cipherText instanceof ArrayBuffer); - assertEquals(cipherText.byteLength, 19); - assertEquals( - new Uint8Array(cipherText), - fixture, - ); - - const plainText = await crypto.subtle.decrypt( - { name: "AES-GCM", iv }, - key, - cipherText, - ); - assert(plainText instanceof ArrayBuffer); - assertEquals(plainText.byteLength, 3); - assertEquals(new Uint8Array(plainText), data); - } -}); - -async function roundTripSecretJwk( - jwk: JsonWebKey, - algId: AlgorithmIdentifier | HmacImportParams, - ops: KeyUsage[], - validateKeys: ( - key: CryptoKey, - originalJwk: JsonWebKey, - exportedJwk: JsonWebKey, - ) => void, -) { - const key = await crypto.subtle.importKey( - "jwk", - jwk, - algId, - true, - ops, - ); - - assert(key instanceof CryptoKey); - assertEquals(key.type, "secret"); - - const exportedKey = await crypto.subtle.exportKey("jwk", key); - - validateKeys(key, jwk, exportedKey); -} - -Deno.test(async function testSecretJwkBase64Url() { - // Test 16bits with "overflow" in 3rd pos of 'quartet', no padding - const keyData = `{ - "kty": "oct", - "k": "xxx", - "alg": "HS512", - "key_ops": ["sign", "verify"], - "ext": true - }`; - - await roundTripSecretJwk( - JSON.parse(keyData), - { name: "HMAC", hash: "SHA-512" }, - ["sign", "verify"], - (key, _orig, exp) => { - assertEquals((key.algorithm as HmacKeyAlgorithm).length, 16); - - assertEquals(exp.k, "xxw"); - }, - ); - - // HMAC 128bits with base64url characters (-_) - await roundTripSecretJwk( - { - kty: "oct", - k: "HnZXRyDKn-_G5Fx4JWR1YA", - alg: "HS256", - "key_ops": ["sign", "verify"], - ext: true, - }, - { name: "HMAC", hash: "SHA-256" }, - ["sign", "verify"], - (key, orig, exp) => { - assertEquals((key.algorithm as HmacKeyAlgorithm).length, 128); - - assertEquals(orig.k, exp.k); - }, - ); - - // HMAC 104bits/(12+1) bytes with base64url characters (-_), padding and overflow in 2rd pos of "quartet" - await roundTripSecretJwk( - { - kty: "oct", - k: "a-_AlFa-2-OmEGa_-z==", - alg: "HS384", - "key_ops": ["sign", "verify"], - ext: true, - }, - { name: "HMAC", hash: "SHA-384" }, - ["sign", "verify"], - (key, _orig, exp) => { - assertEquals((key.algorithm as HmacKeyAlgorithm).length, 104); - - assertEquals("a-_AlFa-2-OmEGa_-w", exp.k); - }, - ); - - // AES-CBC 128bits with base64url characters (-_) no padding - await roundTripSecretJwk( - { - kty: "oct", - k: "_u3K_gEjRWf-7cr-ASNFZw", - alg: "A128CBC", - "key_ops": ["encrypt", "decrypt"], - ext: true, - }, - { name: "AES-CBC" }, - ["encrypt", "decrypt"], - (_key, orig, exp) => { - assertEquals(orig.k, exp.k); - }, - ); - - // AES-CBC 128bits of '1' with padding chars - await roundTripSecretJwk( - { - kty: "oct", - k: "_____________________w==", - alg: "A128CBC", - "key_ops": ["encrypt", "decrypt"], - ext: true, - }, - { name: "AES-CBC" }, - ["encrypt", "decrypt"], - (_key, _orig, exp) => { - assertEquals(exp.k, "_____________________w"); - }, - ); -}); - -Deno.test(async function testAESWrapKey() { - const key = await crypto.subtle.generateKey( - { - name: "AES-KW", - length: 128, - }, - true, - ["wrapKey", "unwrapKey"], - ); - - const hmacKey = await crypto.subtle.generateKey( - { - name: "HMAC", - hash: "SHA-256", - length: 128, - }, - true, - ["sign"], - ); - - //round-trip - // wrap-unwrap-export compare - const wrappedKey = await crypto.subtle.wrapKey( - "raw", - hmacKey, - key, - { - name: "AES-KW", - }, - ); - - assert(wrappedKey instanceof ArrayBuffer); - assertEquals(wrappedKey.byteLength, 16 + 8); // 8 = 'auth tag' - - const unwrappedKey = await crypto.subtle.unwrapKey( - "raw", - wrappedKey, - key, - { - name: "AES-KW", - }, - { - name: "HMAC", - hash: "SHA-256", - }, - true, - ["sign"], - ); - - assert(unwrappedKey instanceof CryptoKey); - assertEquals((unwrappedKey.algorithm as HmacKeyAlgorithm).length, 128); - - const hmacKeyBytes = await crypto.subtle.exportKey("raw", hmacKey); - const unwrappedKeyBytes = await crypto.subtle.exportKey("raw", unwrappedKey); - - assertEquals(new Uint8Array(hmacKeyBytes), new Uint8Array(unwrappedKeyBytes)); -}); - -// https://github.com/denoland/deno/issues/13534 -Deno.test(async function testAesGcmTagLength() { - const key = await crypto.subtle.importKey( - "raw", - new Uint8Array(32), - "AES-GCM", - false, - ["encrypt", "decrypt"], - ); - - const iv = crypto.getRandomValues(new Uint8Array(12)); - - // encrypt won't fail, it will simply truncate the tag - // as expected. - const encrypted = await crypto.subtle.encrypt( - { name: "AES-GCM", iv, tagLength: 96 }, - key, - new Uint8Array(32), - ); - - await assertRejects(async () => { - await crypto.subtle.decrypt( - { name: "AES-GCM", iv, tagLength: 96 }, - key, - encrypted, - ); - }); -}); - -Deno.test(async function ecPrivateKeyMaterialExportSpki() { - // `generateKey` generates a key pair internally stored as "private" key. - const keys = await crypto.subtle.generateKey( - { name: "ECDSA", namedCurve: "P-256" }, - true, - ["sign", "verify"], - ); - - assert(keys.privateKey instanceof CryptoKey); - assert(keys.publicKey instanceof CryptoKey); - - // `exportKey` should be able to perform necessary conversion to export spki. - const spki = await crypto.subtle.exportKey("spki", keys.publicKey); - assert(spki instanceof ArrayBuffer); -}); - -// https://github.com/denoland/deno/issues/13911 -Deno.test(async function importJwkWithUse() { - const jwk = { - "kty": "EC", - "use": "sig", - "crv": "P-256", - "x": "FWZ9rSkLt6Dx9E3pxLybhdM6xgR5obGsj5_pqmnz5J4", - "y": "_n8G69C-A2Xl4xUW2lF0i8ZGZnk_KPYrhv4GbTGu5G4", - }; - - const algorithm = { name: "ECDSA", namedCurve: "P-256" }; - - const key = await crypto.subtle.importKey( - "jwk", - jwk, - algorithm, - true, - ["verify"], - ); - - assert(key instanceof CryptoKey); -}); - -// https://github.com/denoland/deno/issues/14215 -Deno.test(async function exportKeyNotExtractable() { - const key = await crypto.subtle.generateKey( - { - name: "HMAC", - hash: "SHA-512", - }, - false, - ["sign", "verify"], - ); - - assert(key); - assertEquals(key.extractable, false); - - await assertRejects(async () => { - // Should fail - await crypto.subtle.exportKey("raw", key); - }, DOMException); -}); - -// https://github.com/denoland/deno/issues/15126 -Deno.test(async function testImportLeadingZeroesKey() { - const alg = { name: "ECDSA", namedCurve: "P-256" }; - - const jwk = { - kty: "EC", - crv: "P-256", - alg: "ES256", - x: "EvidcdFB1xC6tgfakqZsU9aIURxAJkcX62zHe1Nt6xU", - y: "AHsk6BioGM7MZWeXOE_49AGmtuaXFT3Ill3DYtz9uYg", - d: "WDeYo4o1heCF9l_2VIaClRyIeO16zsMlN8UG6Le9dU8", - "key_ops": ["sign"], - ext: true, - }; - - const key = await crypto.subtle.importKey( - "jwk", - jwk, - alg, - true, - ["sign"], - ); - - assert(key instanceof CryptoKey); - assertEquals(key.type, "private"); -}); - -// https://github.com/denoland/deno/issues/15523 -Deno.test(async function testECspkiRoundTrip() { - const alg = { name: "ECDH", namedCurve: "P-256" }; - const { publicKey } = await crypto.subtle.generateKey(alg, true, [ - "deriveBits", - ]); - const spki = await crypto.subtle.exportKey("spki", publicKey); - await crypto.subtle.importKey("spki", spki, alg, true, []); -}); - -Deno.test(async function testHmacJwkImport() { - await crypto.subtle.importKey( - "jwk", - { - kty: "oct", - use: "sig", - alg: "HS256", - k: "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg", - }, - { name: "HMAC", hash: "SHA-256" }, - false, - ["sign", "verify"], - ); -}); - -Deno.test(async function p521Import() { - const jwk = { - "crv": "P-521", - "ext": true, - "key_ops": [ - "verify", - ], - "kty": "EC", - "x": - "AXkSI8nfkc6bu3fifXGuKKbu08g5LKPfxUNQJJYzzPgmN8XLDzx0C9Sdeejl1XoWGrheKPHl0k4tUmHw0cdInpfj", - "y": - "AT4vjsO0bzVRlN3Wthv9DewncDXS2tlTob5QojV8WX1GzOAikRfWFEP3nspoSv88U447acZAsk5IvgGJuVjgMDlx", - }; - const algorithm = { name: "ECDSA", namedCurve: "P-521" }; - - const key = await crypto.subtle.importKey( - "jwk", - jwk, - algorithm, - true, - ["verify"], - ); - - assert(key instanceof CryptoKey); -}); - -Deno.test(async function p521Generate() { - const algorithm = { name: "ECDSA", namedCurve: "P-521" }; - - const key = await crypto.subtle.generateKey( - algorithm, - true, - ["sign", "verify"], - ); - - assert(key.privateKey instanceof CryptoKey); - assert(key.publicKey instanceof CryptoKey); -}); diff --git a/cli/tests/unit/webgpu_test.ts b/cli/tests/unit/webgpu_test.ts deleted file mode 100644 index 75ae34981..000000000 --- a/cli/tests/unit/webgpu_test.ts +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assert, assertEquals, assertThrows } from "./test_util.ts"; - -let isCI: boolean; -try { - isCI = (Deno.env.get("CI")?.length ?? 0) > 0; -} catch { - isCI = true; -} - -// Skip these tests on linux CI, because the vulkan emulator is not good enough -// yet, and skip on macOS CI because these do not have virtual GPUs. -const isLinuxOrMacCI = - (Deno.build.os === "linux" || Deno.build.os === "darwin") && isCI; -// Skip these tests in WSL because it doesn't have good GPU support. -const isWsl = await checkIsWsl(); - -Deno.test({ - permissions: { read: true, env: true }, - ignore: isWsl || isLinuxOrMacCI, -}, async function webgpuComputePass() { - const adapter = await navigator.gpu.requestAdapter(); - assert(adapter); - - const numbers = [1, 4, 3, 295]; - - const device = await adapter.requestDevice(); - assert(device); - - const shaderCode = await Deno.readTextFile( - "cli/tests/testdata/webgpu/computepass_shader.wgsl", - ); - - const shaderModule = device.createShaderModule({ - code: shaderCode, - }); - - const size = new Uint32Array(numbers).byteLength; - - const stagingBuffer = device.createBuffer({ - size: size, - usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, - }); - - const storageBuffer = device.createBuffer({ - label: "Storage Buffer", - size: size, - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | - GPUBufferUsage.COPY_SRC, - mappedAtCreation: true, - }); - - const buf = new Uint32Array(storageBuffer.getMappedRange()); - - buf.set(numbers); - - storageBuffer.unmap(); - - const computePipeline = device.createComputePipeline({ - layout: "auto", - compute: { - module: shaderModule, - entryPoint: "main", - }, - }); - const bindGroupLayout = computePipeline.getBindGroupLayout(0); - - const bindGroup = device.createBindGroup({ - layout: bindGroupLayout, - entries: [ - { - binding: 0, - resource: { - buffer: storageBuffer, - }, - }, - ], - }); - - const encoder = device.createCommandEncoder(); - - const computePass = encoder.beginComputePass(); - computePass.setPipeline(computePipeline); - computePass.setBindGroup(0, bindGroup); - computePass.insertDebugMarker("compute collatz iterations"); - computePass.dispatchWorkgroups(numbers.length); - computePass.end(); - - encoder.copyBufferToBuffer(storageBuffer, 0, stagingBuffer, 0, size); - - device.queue.submit([encoder.finish()]); - - await stagingBuffer.mapAsync(1); - - const data = stagingBuffer.getMappedRange(); - - assertEquals(new Uint32Array(data), new Uint32Array([0, 2, 7, 55])); - - stagingBuffer.unmap(); - - device.destroy(); - - // TODO(lucacasonato): webgpu spec should add a explicit destroy method for - // adapters. - const resources = Object.keys(Deno.resources()); - Deno.close(Number(resources[resources.length - 1])); -}); - -Deno.test({ - permissions: { read: true, env: true }, - ignore: isWsl || isLinuxOrMacCI, -}, async function webgpuHelloTriangle() { - const adapter = await navigator.gpu.requestAdapter(); - assert(adapter); - - const device = await adapter.requestDevice(); - assert(device); - - const shaderCode = await Deno.readTextFile( - "cli/tests/testdata/webgpu/hellotriangle_shader.wgsl", - ); - - const shaderModule = device.createShaderModule({ - code: shaderCode, - }); - - const pipelineLayout = device.createPipelineLayout({ - bindGroupLayouts: [], - }); - - const renderPipeline = device.createRenderPipeline({ - layout: pipelineLayout, - vertex: { - module: shaderModule, - entryPoint: "vs_main", - }, - fragment: { - module: shaderModule, - entryPoint: "fs_main", - targets: [ - { - format: "rgba8unorm-srgb", - }, - ], - }, - }); - - const dimensions = { - width: 200, - height: 200, - }; - const unpaddedBytesPerRow = dimensions.width * 4; - const align = 256; - const paddedBytesPerRowPadding = (align - unpaddedBytesPerRow % align) % - align; - const paddedBytesPerRow = unpaddedBytesPerRow + paddedBytesPerRowPadding; - - const outputBuffer = device.createBuffer({ - label: "Capture", - size: paddedBytesPerRow * dimensions.height, - usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, - }); - const texture = device.createTexture({ - label: "Capture", - size: dimensions, - format: "rgba8unorm-srgb", - usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, - }); - - const encoder = device.createCommandEncoder(); - const view = texture.createView(); - const renderPass = encoder.beginRenderPass({ - colorAttachments: [ - { - view, - storeOp: "store", - loadOp: "clear", - clearValue: [0, 1, 0, 1], - }, - ], - }); - renderPass.setPipeline(renderPipeline); - renderPass.draw(3, 1); - renderPass.end(); - - encoder.copyTextureToBuffer( - { - texture, - }, - { - buffer: outputBuffer, - bytesPerRow: paddedBytesPerRow, - rowsPerImage: 0, - }, - dimensions, - ); - - const bundle = encoder.finish(); - device.queue.submit([bundle]); - - await outputBuffer.mapAsync(1); - const data = new Uint8Array(outputBuffer.getMappedRange()); - - assertEquals( - data, - await Deno.readFile("cli/tests/testdata/webgpu/hellotriangle.out"), - ); - - outputBuffer.unmap(); - - device.destroy(); - - // TODO(lucacasonato): webgpu spec should add a explicit destroy method for - // adapters. - const resources = Object.keys(Deno.resources()); - Deno.close(Number(resources[resources.length - 1])); -}); - -Deno.test({ - ignore: isWsl || isLinuxOrMacCI, -}, async function webgpuAdapterHasFeatures() { - const adapter = await navigator.gpu.requestAdapter(); - assert(adapter); - assert(adapter.features); - const resources = Object.keys(Deno.resources()); - Deno.close(Number(resources[resources.length - 1])); -}); - -Deno.test({ - ignore: isWsl || isLinuxOrMacCI, -}, async function webgpuNullWindowSurfaceThrows() { - const adapter = await navigator.gpu.requestAdapter(); - assert(adapter); - - const device = await adapter.requestDevice(); - assert(device); - - assertThrows( - () => { - new Deno.UnsafeWindowSurface("cocoa", null, null); - }, - ); - - device.destroy(); - const resources = Object.keys(Deno.resources()); - Deno.close(Number(resources[resources.length - 1])); -}); - -Deno.test(function getPreferredCanvasFormat() { - const preferredFormat = navigator.gpu.getPreferredCanvasFormat(); - assert(preferredFormat === "bgra8unorm" || preferredFormat === "rgba8unorm"); -}); - -async function checkIsWsl() { - return Deno.build.os === "linux" && await hasMicrosoftProcVersion(); - - async function hasMicrosoftProcVersion() { - // https://github.com/microsoft/WSL/issues/423#issuecomment-221627364 - try { - const procVersion = await Deno.readTextFile("/proc/version"); - return /microsoft/i.test(procVersion); - } catch { - return false; - } - } -} diff --git a/cli/tests/unit/websocket_test.ts b/cli/tests/unit/websocket_test.ts deleted file mode 100644 index 42681c187..000000000 --- a/cli/tests/unit/websocket_test.ts +++ /dev/null @@ -1,738 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals, assertThrows, fail } from "./test_util.ts"; - -const servePort = 4248; -const serveUrl = `ws://localhost:${servePort}/`; - -Deno.test({ permissions: "none" }, function websocketPermissionless() { - assertThrows( - () => new WebSocket("ws://localhost"), - Deno.errors.PermissionDenied, - ); -}); - -Deno.test(async function websocketConstructorTakeURLObjectAsParameter() { - const { promise, resolve, reject } = Promise.withResolvers<void>(); - const ws = new WebSocket(new URL("ws://localhost:4242/")); - assertEquals(ws.url, "ws://localhost:4242/"); - ws.onerror = (e) => reject(e); - ws.onopen = () => ws.close(); - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test(async function websocketH2SendSmallPacket() { - const { promise, resolve, reject } = Promise.withResolvers<void>(); - const ws = new WebSocket(new URL("wss://localhost:4249/")); - assertEquals(ws.url, "wss://localhost:4249/"); - let messageCount = 0; - ws.onerror = (e) => reject(e); - ws.onopen = () => { - ws.send("a".repeat(16)); - ws.send("a".repeat(16)); - ws.send("a".repeat(16)); - }; - ws.onmessage = () => { - if (++messageCount == 3) { - ws.close(); - } - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test(async function websocketH2SendLargePacket() { - const { promise, resolve, reject } = Promise.withResolvers<void>(); - const ws = new WebSocket(new URL("wss://localhost:4249/")); - assertEquals(ws.url, "wss://localhost:4249/"); - let messageCount = 0; - ws.onerror = (e) => reject(e); - ws.onopen = () => { - ws.send("a".repeat(65000)); - ws.send("a".repeat(65000)); - ws.send("a".repeat(65000)); - }; - ws.onmessage = () => { - if (++messageCount == 3) { - ws.close(); - } - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test(async function websocketSendLargePacket() { - const { promise, resolve, reject } = Promise.withResolvers<void>(); - const ws = new WebSocket(new URL("wss://localhost:4243/")); - assertEquals(ws.url, "wss://localhost:4243/"); - ws.onerror = (e) => reject(e); - ws.onopen = () => { - ws.send("a".repeat(65000)); - }; - ws.onmessage = () => { - ws.close(); - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test(async function websocketSendLargeBinaryPacket() { - const { promise, resolve, reject } = Promise.withResolvers<void>(); - const ws = new WebSocket(new URL("wss://localhost:4243/")); - ws.binaryType = "arraybuffer"; - assertEquals(ws.url, "wss://localhost:4243/"); - ws.onerror = (e) => reject(e); - ws.onopen = () => { - ws.send(new Uint8Array(65000)); - }; - ws.onmessage = (msg: MessageEvent) => { - assertEquals(msg.data.byteLength, 65000); - ws.close(); - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test(async function websocketSendLargeBlobPacket() { - const { promise, resolve, reject } = Promise.withResolvers<void>(); - const ws = new WebSocket(new URL("wss://localhost:4243/")); - ws.binaryType = "arraybuffer"; - assertEquals(ws.url, "wss://localhost:4243/"); - ws.onerror = (e) => reject(e); - ws.onopen = () => { - ws.send(new Blob(["a".repeat(65000)])); - }; - ws.onmessage = (msg: MessageEvent) => { - assertEquals(msg.data.byteLength, 65000); - ws.close(); - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -// https://github.com/denoland/deno/pull/17762 -// https://github.com/denoland/deno/issues/17761 -Deno.test(async function websocketPingPong() { - const { promise, resolve, reject } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4245/"); - assertEquals(ws.url, "ws://localhost:4245/"); - ws.onerror = (e) => reject(e); - ws.onmessage = (e) => { - ws.send(e.data); - }; - ws.onclose = () => { - resolve(); - }; - await promise; - ws.close(); -}); - -// TODO(mmastrac): This requires us to ignore bad certs -// Deno.test(async function websocketSecureConnect() { -// const { promise, resolve } = Promise.withResolvers<void>(); -// const ws = new WebSocket("wss://localhost:4243/"); -// assertEquals(ws.url, "wss://localhost:4243/"); -// ws.onerror = (error) => { -// console.log(error); -// fail(); -// }; -// ws.onopen = () => ws.close(); -// ws.onclose = () => { -// resolve(); -// }; -// await promise; -// }); - -// https://github.com/denoland/deno/issues/18700 -Deno.test( - { sanitizeOps: false, sanitizeResources: false }, - async function websocketWriteLock() { - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: (req) => { - const { socket, response } = Deno.upgradeWebSocket(req); - socket.onopen = function () { - setTimeout(() => socket.send("Hello"), 500); - }; - socket.onmessage = function (e) { - assertEquals(e.data, "Hello"); - ac.abort(); - }; - return response; - }, - signal: ac.signal, - onListen: () => listeningDeferred.resolve(), - hostname: "localhost", - port: servePort, - }); - - await listeningDeferred.promise; - const deferred = Promise.withResolvers<void>(); - const ws = new WebSocket(serveUrl); - assertEquals(ws.url, serveUrl); - ws.onerror = () => fail(); - ws.onmessage = (e) => { - assertEquals(e.data, "Hello"); - setTimeout(() => { - ws.send(e.data); - }, 1000); - deferred.resolve(); - }; - ws.onclose = () => { - deferred.resolve(); - }; - - await Promise.all([deferred.promise, server.finished]); - ws.close(); - }, -); - -// https://github.com/denoland/deno/issues/18775 -Deno.test({ - sanitizeOps: false, - sanitizeResources: false, -}, async function websocketDoubleClose() { - const deferred = Promise.withResolvers<void>(); - - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: (req) => { - const { response, socket } = Deno.upgradeWebSocket(req); - let called = false; - socket.onopen = () => socket.send("Hello"); - socket.onmessage = () => { - assert(!called); - called = true; - socket.send("bye"); - socket.close(); - }; - socket.onclose = () => ac.abort(); - socket.onerror = () => fail(); - return response; - }, - signal: ac.signal, - onListen: () => listeningDeferred.resolve(), - hostname: "localhost", - port: servePort, - }); - - await listeningDeferred.promise; - - const ws = new WebSocket(serveUrl); - assertEquals(ws.url, serveUrl); - ws.onerror = () => fail(); - ws.onmessage = (m: MessageEvent) => { - if (m.data == "Hello") ws.send("bye"); - }; - ws.onclose = () => { - deferred.resolve(); - }; - await Promise.all([deferred.promise, server.finished]); -}); - -// https://github.com/denoland/deno/issues/19483 -Deno.test({ - sanitizeOps: false, - sanitizeResources: false, -}, async function websocketCloseFlushes() { - const deferred = Promise.withResolvers<void>(); - - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: (req) => { - const { response, socket } = Deno.upgradeWebSocket(req); - socket.onopen = () => socket.send("Hello"); - socket.onmessage = () => { - socket.send("Bye"); - socket.close(); - }; - socket.onclose = () => ac.abort(); - socket.onerror = () => fail(); - return response; - }, - signal: ac.signal, - onListen: () => listeningDeferred.resolve(), - hostname: "localhost", - port: servePort, - }); - - await listeningDeferred.promise; - - const ws = new WebSocket(serveUrl); - assertEquals(ws.url, serveUrl); - let seenBye = false; - ws.onerror = () => fail(); - ws.onmessage = ({ data }) => { - if (data == "Hello") { - ws.send("Hello!"); - } else { - assertEquals(data, "Bye"); - seenBye = true; - } - }; - ws.onclose = () => { - deferred.resolve(); - }; - await Promise.all([deferred.promise, server.finished]); - - assert(seenBye); -}); - -Deno.test( - { sanitizeOps: false }, - function websocketConstructorWithPrototypePollution() { - const originalSymbolIterator = Array.prototype[Symbol.iterator]; - try { - Array.prototype[Symbol.iterator] = () => { - throw Error("unreachable"); - }; - assertThrows(() => { - new WebSocket( - new URL("ws://localhost:4242/"), - // Allow `Symbol.iterator` to be called in WebIDL conversion to `sequence<DOMString>` - // deno-lint-ignore no-explicit-any - ["soap", "soap"].values() as any, - ); - }, DOMException); - } finally { - Array.prototype[Symbol.iterator] = originalSymbolIterator; - } - }, -); - -Deno.test(async function websocketTlsSocketWorks() { - const cert = await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"); - const key = await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"); - - const messages: string[] = [], - errors: { server?: Event; client?: Event }[] = []; - const promise = new Promise((okay, nope) => { - const ac = new AbortController(); - const server = Deno.serve({ - handler: (req) => { - const { response, socket } = Deno.upgradeWebSocket(req); - socket.onopen = () => socket.send("ping"); - socket.onmessage = (e) => { - messages.push(e.data); - socket.close(); - }; - socket.onerror = (e) => errors.push({ server: e }); - socket.onclose = () => ac.abort(); - return response; - }, - signal: ac.signal, - hostname: "localhost", - port: servePort, - cert, - key, - }); - setTimeout(() => { - const ws = new WebSocket(`wss://localhost:${servePort}`); - ws.onmessage = (e) => { - messages.push(e.data); - ws.send("pong"); - }; - ws.onerror = (e) => { - errors.push({ client: e }); - nope(); - }; - ws.onclose = () => okay(server.finished); - }, 1000); - }); - - const finished = await promise; - - assertEquals(errors, []); - assertEquals(messages, ["ping", "pong"]); - - await finished; -}); - -// https://github.com/denoland/deno/issues/15340 -Deno.test( - async function websocketServerFieldInit() { - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: (req) => { - const { socket, response } = Deno.upgradeWebSocket(req, { - idleTimeout: 0, - }); - socket.onopen = function () { - assert(typeof socket.url == "string"); - assert(socket.readyState == WebSocket.OPEN); - assert(socket.protocol == ""); - assert(socket.binaryType == "arraybuffer"); - socket.close(); - }; - socket.onclose = () => ac.abort(); - return response; - }, - signal: ac.signal, - onListen: () => listeningDeferred.resolve(), - hostname: "localhost", - port: servePort, - }); - - await listeningDeferred.promise; - const deferred = Promise.withResolvers<void>(); - const ws = new WebSocket(serveUrl); - assertEquals(ws.url, serveUrl); - ws.onerror = () => fail(); - ws.onclose = () => { - deferred.resolve(); - }; - - await Promise.all([deferred.promise, server.finished]); - }, -); - -Deno.test( - { sanitizeOps: false }, - async function websocketServerGetsGhosted() { - const ac = new AbortController(); - const listeningDeferred = Promise.withResolvers<void>(); - - const server = Deno.serve({ - handler: (req) => { - const { socket, response } = Deno.upgradeWebSocket(req, { - idleTimeout: 2, - }); - socket.onerror = () => socket.close(); - socket.onclose = () => ac.abort(); - return response; - }, - signal: ac.signal, - onListen: () => listeningDeferred.resolve(), - hostname: "localhost", - port: servePort, - }); - - await listeningDeferred.promise; - const r = await fetch("http://localhost:4545/ghost_ws_client"); - assertEquals(r.status, 200); - await r.body?.cancel(); - - await server.finished; - }, -); - -Deno.test("invalid scheme", () => { - assertThrows(() => new WebSocket("foo://localhost:4242")); -}); - -Deno.test("fragment", () => { - assertThrows(() => new WebSocket("ws://localhost:4242/#")); - assertThrows(() => new WebSocket("ws://localhost:4242/#foo")); -}); - -Deno.test("duplicate protocols", () => { - assertThrows(() => new WebSocket("ws://localhost:4242", ["foo", "foo"])); -}); - -Deno.test("invalid server", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:2121"); - let err = false; - ws.onerror = () => { - err = true; - }; - ws.onclose = () => { - if (err) { - resolve(); - } else { - fail(); - } - }; - ws.onopen = () => fail(); - await promise; -}); - -Deno.test("connect & close", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - ws.onerror = () => fail(); - ws.onopen = () => { - ws.close(); - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test("connect & abort", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - ws.close(); - let err = false; - ws.onerror = () => { - err = true; - }; - ws.onclose = () => { - if (err) { - resolve(); - } else { - fail(); - } - }; - ws.onopen = () => fail(); - await promise; -}); - -Deno.test("connect & close custom valid code", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - ws.onerror = () => fail(); - ws.onopen = () => ws.close(1000); - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test("connect & close custom invalid code", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - ws.onerror = () => fail(); - ws.onopen = () => { - assertThrows(() => ws.close(1001)); - ws.close(); - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test("connect & close custom valid reason", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - ws.onerror = () => fail(); - ws.onopen = () => ws.close(1000, "foo"); - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test("connect & close custom invalid reason", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - ws.onerror = () => fail(); - ws.onopen = () => { - assertThrows(() => ws.close(1000, "".padEnd(124, "o"))); - ws.close(); - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test("echo string", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - ws.onerror = () => fail(); - ws.onopen = () => ws.send("foo"); - ws.onmessage = (e) => { - assertEquals(e.data, "foo"); - ws.close(); - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test("echo string tls", async () => { - const deferred1 = Promise.withResolvers<void>(); - const deferred2 = Promise.withResolvers<void>(); - const ws = new WebSocket("wss://localhost:4243"); - ws.onerror = () => fail(); - ws.onopen = () => ws.send("foo"); - ws.onmessage = (e) => { - assertEquals(e.data, "foo"); - ws.close(); - deferred1.resolve(); - }; - ws.onclose = () => { - deferred2.resolve(); - }; - await deferred1.promise; - await deferred2.promise; -}); - -Deno.test("websocket error", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("wss://localhost:4242"); - ws.onopen = () => fail(); - ws.onerror = (err) => { - assert(err instanceof ErrorEvent); - assertEquals( - err.message, - "NetworkError: failed to connect to WebSocket: received corrupt message of type InvalidContentType", - ); - resolve(); - }; - await promise; -}); - -Deno.test("echo blob with binaryType blob", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - const blob = new Blob(["foo"]); - ws.onerror = () => fail(); - ws.onopen = () => ws.send(blob); - ws.onmessage = (e) => { - e.data.text().then((actual: string) => { - blob.text().then((expected) => { - assertEquals(actual, expected); - }); - }); - ws.close(); - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test("echo blob with binaryType arraybuffer", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - ws.binaryType = "arraybuffer"; - const blob = new Blob(["foo"]); - ws.onerror = () => fail(); - ws.onopen = () => ws.send(blob); - ws.onmessage = (e) => { - blob.arrayBuffer().then((expected) => { - assertEquals(e.data, expected); - }); - ws.close(); - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test("echo uint8array with binaryType blob", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - const uint = new Uint8Array([102, 111, 111]); - ws.onerror = () => fail(); - ws.onopen = () => ws.send(uint); - ws.onmessage = (e) => { - e.data.arrayBuffer().then((actual: ArrayBuffer) => { - assertEquals(actual, uint.buffer); - }); - ws.close(); - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test("echo uint8array with binaryType arraybuffer", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - ws.binaryType = "arraybuffer"; - const uint = new Uint8Array([102, 111, 111]); - ws.onerror = () => fail(); - ws.onopen = () => ws.send(uint); - ws.onmessage = (e) => { - assertEquals(e.data, uint.buffer); - ws.close(); - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test("echo arraybuffer with binaryType blob", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - const buffer = new ArrayBuffer(3); - ws.onerror = () => fail(); - ws.onopen = () => ws.send(buffer); - ws.onmessage = (e) => { - e.data.arrayBuffer().then((actual: ArrayBuffer) => { - assertEquals(actual, buffer); - }); - ws.close(); - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test("echo arraybuffer with binaryType arraybuffer", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - ws.binaryType = "arraybuffer"; - const buffer = new ArrayBuffer(3); - ws.onerror = () => fail(); - ws.onopen = () => ws.send(buffer); - ws.onmessage = (e) => { - assertEquals(e.data, buffer); - ws.close(); - }; - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test("Event Handlers order", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4242"); - const arr: number[] = []; - ws.onerror = () => fail(); - ws.addEventListener("message", () => arr.push(1)); - ws.onmessage = () => fail(); - ws.addEventListener("message", () => { - arr.push(3); - ws.close(); - assertEquals(arr, [1, 2, 3]); - }); - ws.onmessage = () => arr.push(2); - ws.onopen = () => ws.send("Echo"); - ws.onclose = () => { - resolve(); - }; - await promise; -}); - -Deno.test("Close without frame", async () => { - const { promise, resolve } = Promise.withResolvers<void>(); - const ws = new WebSocket("ws://localhost:4244"); - ws.onerror = () => fail(); - ws.onclose = (e) => { - assertEquals(e.code, 1005); - resolve(); - }; - await promise; -}); diff --git a/cli/tests/unit/websocketstream_test.ts.disabled b/cli/tests/unit/websocketstream_test.ts.disabled deleted file mode 100644 index eaedb71bd..000000000 --- a/cli/tests/unit/websocketstream_test.ts.disabled +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { - assertEquals, - assertNotEquals, - assertRejects, - assertThrows, - unreachable, -} from "@test_util/std/assert/mod.ts"; - -Deno.test("fragment", () => { - assertThrows(() => new WebSocketStream("ws://localhost:4242/#")); - assertThrows(() => new WebSocketStream("ws://localhost:4242/#foo")); -}); - -Deno.test("duplicate protocols", () => { - assertThrows(() => - new WebSocketStream("ws://localhost:4242", { - protocols: ["foo", "foo"], - }) - ); -}); - -Deno.test( - "connect & close custom valid code", - { sanitizeOps: false }, - async () => { - const ws = new WebSocketStream("ws://localhost:4242"); - await ws.opened; - ws.close({ code: 1000 }); - await ws.closed; - }, -); - -Deno.test( - "connect & close custom invalid reason", - { sanitizeOps: false }, - async () => { - const ws = new WebSocketStream("ws://localhost:4242"); - await ws.opened; - assertThrows(() => ws.close({ code: 1000, reason: "".padEnd(124, "o") })); - ws.close(); - await ws.closed; - }, -); - -Deno.test("echo string", { sanitizeOps: false }, async () => { - const ws = new WebSocketStream("ws://localhost:4242"); - const { readable, writable } = await ws.opened; - await writable.getWriter().write("foo"); - const res = await readable.getReader().read(); - assertEquals(res.value, "foo"); - ws.close(); - await ws.closed; -}); - -// TODO(mmastrac): This fails -- perhaps it isn't respecting the TLS settings? -Deno.test("echo string tls", { ignore: true }, async () => { - const ws = new WebSocketStream("wss://localhost:4243"); - const { readable, writable } = await ws.opened; - await writable.getWriter().write("foo"); - const res = await readable.getReader().read(); - assertEquals(res.value, "foo"); - ws.close(); - await ws.closed; -}); - -Deno.test("websocket error", { sanitizeOps: false }, async () => { - const ws = new WebSocketStream("wss://localhost:4242"); - await Promise.all([ - // TODO(mmastrac): this exception should be tested - assertRejects( - () => ws.opened, - // Deno.errors.UnexpectedEof, - // "tls handshake eof", - ), - // TODO(mmastrac): this exception should be tested - assertRejects( - () => ws.closed, - // Deno.errors.UnexpectedEof, - // "tls handshake eof", - ), - ]); -}); - -Deno.test("echo uint8array", { sanitizeOps: false }, async () => { - const ws = new WebSocketStream("ws://localhost:4242"); - const { readable, writable } = await ws.opened; - const uint = new Uint8Array([102, 111, 111]); - await writable.getWriter().write(uint); - const res = await readable.getReader().read(); - assertEquals(res.value, uint); - ws.close(); - await ws.closed; -}); - -Deno.test("aborting immediately throws an AbortError", async () => { - const controller = new AbortController(); - const wss = new WebSocketStream("ws://localhost:4242", { - signal: controller.signal, - }); - controller.abort(); - // TODO(mmastrac): this exception should be tested - await assertRejects( - () => wss.opened, - // (error: Error) => { - // assert(error instanceof DOMException); - // assertEquals(error.name, "AbortError"); - // }, - ); - // TODO(mmastrac): this exception should be tested - await assertRejects( - () => wss.closed, - // (error: Error) => { - // assert(error instanceof DOMException); - // assertEquals(error.name, "AbortError"); - // }, - ); -}); - -Deno.test("aborting immediately with a reason throws that reason", async () => { - const controller = new AbortController(); - const wss = new WebSocketStream("ws://localhost:4242", { - signal: controller.signal, - }); - const abortReason = new Error(); - controller.abort(abortReason); - // TODO(mmastrac): this exception should be tested - await assertRejects( - () => wss.opened, - // (error: Error) => assertEquals(error, abortReason), - ); - // TODO(mmastrac): this exception should be tested - await assertRejects( - () => wss.closed, - // (error: Error) => assertEquals(error, abortReason), - ); -}); - -Deno.test("aborting immediately with a primitive as reason throws that primitive", async () => { - const controller = new AbortController(); - const wss = new WebSocketStream("ws://localhost:4242", { - signal: controller.signal, - }); - controller.abort("Some string"); - await wss.opened.then( - () => unreachable(), - (e) => assertEquals(e, "Some string"), - ); - await wss.closed.then( - () => unreachable(), - (e) => assertEquals(e, "Some string"), - ); -}); - -Deno.test("headers", { sanitizeOps: false }, async () => { - const listener = Deno.listen({ port: 4512 }); - const promise = (async () => { - const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); - const { request, respondWith } = (await httpConn.nextRequest())!; - assertEquals(request.headers.get("x-some-header"), "foo"); - const { response, socket } = Deno.upgradeWebSocket(request); - socket.onopen = () => socket.close(); - const p = new Promise<void>((resolve) => { - socket.onopen = () => socket.close(); - socket.onclose = () => resolve(); - }); - await respondWith(response); - await p; - })(); - - const ws = new WebSocketStream("ws://localhost:4512", { - headers: [["x-some-header", "foo"]], - }); - await ws.opened; - await promise; - await ws.closed; - listener.close(); -}); - -Deno.test("forbidden headers", async () => { - const forbiddenHeaders = [ - "sec-websocket-accept", - "sec-websocket-extensions", - "sec-websocket-key", - "sec-websocket-protocol", - "sec-websocket-version", - "upgrade", - "connection", - ]; - - const listener = Deno.listen({ port: 4512 }); - const promise = (async () => { - const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); - const { request, respondWith } = (await httpConn.nextRequest())!; - for (const [key] of request.headers) { - assertNotEquals(key, "foo"); - } - const { response, socket } = Deno.upgradeWebSocket(request); - const p = new Promise<void>((resolve) => { - socket.onopen = () => socket.close(); - socket.onclose = () => resolve(); - }); - await respondWith(response); - await p; - })(); - - const ws = new WebSocketStream("ws://localhost:4512", { - headers: forbiddenHeaders.map((header) => [header, "foo"]), - }); - await ws.opened; - await promise; - await ws.closed; - listener.close(); -}); - -Deno.test("sync close with empty stream", { sanitizeOps: false }, async () => { - const listener = Deno.listen({ port: 4512 }); - const promise = (async () => { - const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); - const { request, respondWith } = (await httpConn.nextRequest())!; - const { response, socket } = Deno.upgradeWebSocket(request); - const p = new Promise<void>((resolve) => { - socket.onopen = () => { - socket.send("first message"); - socket.send("second message"); - }; - socket.onclose = () => resolve(); - }); - await respondWith(response); - await p; - })(); - - const ws = new WebSocketStream("ws://localhost:4512"); - const { readable } = await ws.opened; - const reader = readable.getReader(); - const firstMessage = await reader.read(); - assertEquals(firstMessage.value, "first message"); - const secondMessage = await reader.read(); - assertEquals(secondMessage.value, "second message"); - ws.close({ code: 1000 }); - await ws.closed; - await promise; - listener.close(); -}); - -Deno.test( - "sync close with unread messages in stream", - { sanitizeOps: false }, - async () => { - const listener = Deno.listen({ port: 4512 }); - const promise = (async () => { - const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); - const { request, respondWith } = (await httpConn.nextRequest())!; - const { response, socket } = Deno.upgradeWebSocket(request); - const p = new Promise<void>((resolve) => { - socket.onopen = () => { - socket.send("first message"); - socket.send("second message"); - socket.send("third message"); - socket.send("fourth message"); - }; - socket.onclose = () => resolve(); - }); - await respondWith(response); - await p; - })(); - - const ws = new WebSocketStream("ws://localhost:4512"); - const { readable } = await ws.opened; - const reader = readable.getReader(); - const firstMessage = await reader.read(); - assertEquals(firstMessage.value, "first message"); - const secondMessage = await reader.read(); - assertEquals(secondMessage.value, "second message"); - ws.close({ code: 1000 }); - await ws.closed; - await promise; - listener.close(); - }, -); - -// TODO(mmastrac): Failed on CI, disabled -Deno.test("async close with empty stream", { ignore: true }, async () => { - const listener = Deno.listen({ port: 4512 }); - const promise = (async () => { - const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); - const { request, respondWith } = (await httpConn.nextRequest())!; - const { response, socket } = Deno.upgradeWebSocket(request); - const p = new Promise<void>((resolve) => { - socket.onopen = () => { - socket.send("first message"); - socket.send("second message"); - }; - socket.onclose = () => resolve(); - }); - await respondWith(response); - await p; - })(); - - const ws = new WebSocketStream("ws://localhost:4512"); - const { readable } = await ws.opened; - const reader = readable.getReader(); - const firstMessage = await reader.read(); - assertEquals(firstMessage.value, "first message"); - const secondMessage = await reader.read(); - assertEquals(secondMessage.value, "second message"); - setTimeout(() => { - ws.close({ code: 1000 }); - }, 0); - await ws.closed; - await promise; - listener.close(); -}); - -// TODO(mmastrac): Failed on CI, disabled -Deno.test( - "async close with unread messages in stream", - { ignore: true }, - async () => { - const listener = Deno.listen({ port: 4512 }); - const promise = (async () => { - const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); - const { request, respondWith } = (await httpConn.nextRequest())!; - const { response, socket } = Deno.upgradeWebSocket(request); - const p = new Promise<void>((resolve) => { - socket.onopen = () => { - socket.send("first message"); - socket.send("second message"); - socket.send("third message"); - socket.send("fourth message"); - }; - socket.onclose = () => resolve(); - }); - await respondWith(response); - await p; - })(); - - const ws = new WebSocketStream("ws://localhost:4512"); - const { readable } = await ws.opened; - const reader = readable.getReader(); - const firstMessage = await reader.read(); - assertEquals(firstMessage.value, "first message"); - const secondMessage = await reader.read(); - assertEquals(secondMessage.value, "second message"); - setTimeout(() => { - ws.close({ code: 1000 }); - }, 0); - await ws.closed; - await promise; - listener.close(); - }, -); diff --git a/cli/tests/unit/webstorage_test.ts b/cli/tests/unit/webstorage_test.ts deleted file mode 100644 index 9dc560af1..000000000 --- a/cli/tests/unit/webstorage_test.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -// deno-lint-ignore-file no-explicit-any - -import { assert, assertEquals, assertThrows } from "./test_util.ts"; - -Deno.test({ permissions: "none" }, function webStoragesReassignable() { - // Can reassign to web storages - globalThis.localStorage = 1 as any; - globalThis.sessionStorage = 1 as any; - // The actual values don't change - assert(globalThis.localStorage instanceof globalThis.Storage); - assert(globalThis.sessionStorage instanceof globalThis.Storage); -}); - -Deno.test(function webstorageSizeLimit() { - localStorage.clear(); - assertThrows( - () => { - localStorage.setItem("k", "v".repeat(15 * 1024 * 1024)); - }, - Error, - "Exceeded maximum storage size", - ); - assertEquals(localStorage.getItem("k"), null); - assertThrows( - () => { - localStorage.setItem("k".repeat(15 * 1024 * 1024), "v"); - }, - Error, - "Exceeded maximum storage size", - ); - assertThrows( - () => { - localStorage.setItem( - "k".repeat(5 * 1024 * 1024), - "v".repeat(5 * 1024 * 1024), - ); - }, - Error, - "Exceeded maximum storage size", - ); -}); - -Deno.test(function webstorageProxy() { - localStorage.clear(); - localStorage.foo = "foo"; - assertEquals(localStorage.foo, "foo"); - const symbol = Symbol("bar"); - localStorage[symbol as any] = "bar"; - assertEquals(localStorage[symbol as any], "bar"); - assertEquals(symbol in localStorage, true); -}); diff --git a/cli/tests/unit/worker_permissions_test.ts b/cli/tests/unit/worker_permissions_test.ts deleted file mode 100644 index 28bf9f92a..000000000 --- a/cli/tests/unit/worker_permissions_test.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "./test_util.ts"; - -Deno.test( - { permissions: { env: true, read: true } }, - async function workerEnvArrayPermissions() { - const { promise, resolve } = Promise.withResolvers<boolean[]>(); - - const worker = new Worker( - import.meta.resolve( - "../testdata/workers/env_read_check_worker.js", - ), - { type: "module", deno: { permissions: { env: ["test", "OTHER"] } } }, - ); - - worker.onmessage = ({ data }) => { - resolve(data.permissions); - }; - - worker.postMessage({ - names: ["test", "TEST", "asdf", "OTHER"], - }); - - const permissions = await promise; - worker.terminate(); - - if (Deno.build.os === "windows") { - // windows ignores case - assertEquals(permissions, [true, true, false, true]); - } else { - assertEquals(permissions, [true, false, false, true]); - } - }, -); diff --git a/cli/tests/unit/worker_test.ts b/cli/tests/unit/worker_test.ts deleted file mode 100644 index eea0e8106..000000000 --- a/cli/tests/unit/worker_test.ts +++ /dev/null @@ -1,843 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -// Requires to be run with `--allow-net` flag - -import { - assert, - assertEquals, - assertMatch, - assertThrows, -} from "@test_util/std/assert/mod.ts"; - -function resolveWorker(worker: string): string { - return import.meta.resolve(`../testdata/workers/${worker}`); -} - -Deno.test( - { permissions: { read: true } }, - function utimeSyncFileSuccess() { - const w = new Worker( - resolveWorker("worker_types.ts"), - { type: "module" }, - ); - assert(w); - w.terminate(); - }, -); - -Deno.test({ - name: "worker terminate", - fn: async function () { - const jsWorker = new Worker( - resolveWorker("test_worker.js"), - { type: "module" }, - ); - const tsWorker = new Worker( - resolveWorker("test_worker.ts"), - { type: "module", name: "tsWorker" }, - ); - - const deferred1 = Promise.withResolvers<string>(); - jsWorker.onmessage = (e) => { - deferred1.resolve(e.data); - }; - - const deferred2 = Promise.withResolvers<string>(); - tsWorker.onmessage = (e) => { - deferred2.resolve(e.data); - }; - - jsWorker.postMessage("Hello World"); - assertEquals(await deferred1.promise, "Hello World"); - tsWorker.postMessage("Hello World"); - assertEquals(await deferred2.promise, "Hello World"); - tsWorker.terminate(); - jsWorker.terminate(); - }, -}); - -Deno.test({ - name: "worker from data url", - async fn() { - const tsWorker = new Worker( - "data:application/typescript;base64,aWYgKHNlbGYubmFtZSAhPT0gInRzV29ya2VyIikgewogIHRocm93IEVycm9yKGBJbnZhbGlkIHdvcmtlciBuYW1lOiAke3NlbGYubmFtZX0sIGV4cGVjdGVkIHRzV29ya2VyYCk7Cn0KCm9ubWVzc2FnZSA9IGZ1bmN0aW9uIChlKTogdm9pZCB7CiAgcG9zdE1lc3NhZ2UoZS5kYXRhKTsKICBjbG9zZSgpOwp9Owo=", - { type: "module", name: "tsWorker" }, - ); - - const { promise, resolve } = Promise.withResolvers<string>(); - tsWorker.onmessage = (e) => { - resolve(e.data); - }; - - tsWorker.postMessage("Hello World"); - assertEquals(await promise, "Hello World"); - tsWorker.terminate(); - }, -}); - -Deno.test({ - name: "worker nested", - fn: async function () { - const nestedWorker = new Worker( - resolveWorker("nested_worker.js"), - { type: "module", name: "nested" }, - ); - - // deno-lint-ignore no-explicit-any - const { promise, resolve } = Promise.withResolvers<any>(); - nestedWorker.onmessage = (e) => { - resolve(e.data); - }; - - nestedWorker.postMessage("Hello World"); - assertEquals(await promise, { type: "msg", text: "Hello World" }); - nestedWorker.terminate(); - }, -}); - -Deno.test({ - name: "worker throws when executing", - fn: async function () { - const throwingWorker = new Worker( - resolveWorker("throwing_worker.js"), - { type: "module" }, - ); - - const { promise, resolve } = Promise.withResolvers<string>(); - // deno-lint-ignore no-explicit-any - throwingWorker.onerror = (e: any) => { - e.preventDefault(); - resolve(e.message); - }; - - assertMatch( - await promise as string, - /Uncaught \(in promise\) Error: Thrown error/, - ); - throwingWorker.terminate(); - }, -}); - -Deno.test({ - name: "worker globals", - fn: async function () { - const workerOptions: WorkerOptions = { type: "module" }; - const w = new Worker( - resolveWorker("worker_globals.ts"), - workerOptions, - ); - - const { promise, resolve } = Promise.withResolvers<string>(); - w.onmessage = (e) => { - resolve(e.data); - }; - - w.postMessage("Hello, world!"); - assertEquals(await promise, "true, true, true, true"); - w.terminate(); - }, -}); - -Deno.test({ - name: "worker navigator", - fn: async function () { - const workerOptions: WorkerOptions = { type: "module" }; - const w = new Worker( - resolveWorker("worker_navigator.ts"), - workerOptions, - ); - - const { promise, resolve } = Promise.withResolvers<string>(); - w.onmessage = (e) => { - resolve(e.data); - }; - - w.postMessage("Hello, world!"); - assertEquals(await promise, "string, object, string, number"); - w.terminate(); - }, -}); - -Deno.test({ - name: "worker fetch API", - fn: async function () { - const fetchingWorker = new Worker( - resolveWorker("fetching_worker.js"), - { type: "module" }, - ); - - const { promise, resolve, reject } = Promise.withResolvers<string>(); - // deno-lint-ignore no-explicit-any - fetchingWorker.onerror = (e: any) => { - e.preventDefault(); - reject(e.message); - }; - // Defer promise.resolve() to allow worker to shut down - fetchingWorker.onmessage = (e) => { - resolve(e.data); - }; - - assertEquals(await promise, "Done!"); - fetchingWorker.terminate(); - }, -}); - -Deno.test({ - name: "worker terminate busy loop", - fn: async function () { - const { promise, resolve } = Promise.withResolvers<number>(); - - const busyWorker = new Worker( - resolveWorker("busy_worker.js"), - { type: "module" }, - ); - - let testResult = 0; - - busyWorker.onmessage = (e) => { - testResult = e.data; - if (testResult >= 10000) { - busyWorker.terminate(); - busyWorker.onmessage = (_e) => { - throw new Error("unreachable"); - }; - setTimeout(() => { - resolve(testResult); - }, 100); - } - }; - - busyWorker.postMessage("ping"); - assertEquals(await promise, 10000); - }, -}); - -Deno.test({ - name: "worker race condition", - fn: async function () { - // See issue for details - // https://github.com/denoland/deno/issues/4080 - const { promise, resolve } = Promise.withResolvers<void>(); - - const racyWorker = new Worker( - resolveWorker("racy_worker.js"), - { type: "module" }, - ); - - racyWorker.onmessage = (_e) => { - setTimeout(() => { - resolve(); - }, 100); - }; - - racyWorker.postMessage("START"); - await promise; - }, -}); - -Deno.test({ - name: "worker is event listener", - fn: async function () { - let messageHandlersCalled = 0; - let errorHandlersCalled = 0; - - const deferred1 = Promise.withResolvers<void>(); - const deferred2 = Promise.withResolvers<void>(); - - const worker = new Worker( - resolveWorker("event_worker.js"), - { type: "module" }, - ); - - worker.onmessage = (_e: Event) => { - messageHandlersCalled++; - }; - worker.addEventListener("message", (_e: Event) => { - messageHandlersCalled++; - }); - worker.addEventListener("message", (_e: Event) => { - messageHandlersCalled++; - deferred1.resolve(); - }); - - worker.onerror = (e) => { - errorHandlersCalled++; - e.preventDefault(); - }; - worker.addEventListener("error", (_e: Event) => { - errorHandlersCalled++; - }); - worker.addEventListener("error", (_e: Event) => { - errorHandlersCalled++; - deferred2.resolve(); - }); - - worker.postMessage("ping"); - await deferred1.promise; - assertEquals(messageHandlersCalled, 3); - - worker.postMessage("boom"); - await deferred2.promise; - assertEquals(errorHandlersCalled, 3); - worker.terminate(); - }, -}); - -Deno.test({ - name: "worker scope is event listener", - fn: async function () { - const worker = new Worker( - resolveWorker("event_worker_scope.js"), - { type: "module" }, - ); - - // deno-lint-ignore no-explicit-any - const { promise, resolve } = Promise.withResolvers<any>(); - worker.onmessage = (e: MessageEvent) => { - resolve(e.data); - }; - worker.onerror = (_e) => { - throw new Error("unreachable"); - }; - - worker.postMessage("boom"); - worker.postMessage("ping"); - assertEquals(await promise, { - messageHandlersCalled: 4, - errorHandlersCalled: 4, - }); - worker.terminate(); - }, -}); - -Deno.test({ - name: "worker with Deno namespace", - fn: async function () { - const denoWorker = new Worker( - resolveWorker("deno_worker.ts"), - { type: "module", deno: { permissions: "inherit" } }, - ); - - const { promise, resolve } = Promise.withResolvers<string>(); - denoWorker.onmessage = (e) => { - denoWorker.terminate(); - resolve(e.data); - }; - - denoWorker.postMessage("Hello World"); - assertEquals(await promise, "Hello World"); - }, -}); - -Deno.test({ - name: "worker with crypto in scope", - fn: async function () { - const w = new Worker( - resolveWorker("worker_crypto.js"), - { type: "module" }, - ); - - const { promise, resolve } = Promise.withResolvers<boolean>(); - w.onmessage = (e) => { - resolve(e.data); - }; - - w.postMessage(null); - assertEquals(await promise, true); - w.terminate(); - }, -}); - -Deno.test({ - name: "Worker event handler order", - fn: async function () { - const { promise, resolve } = Promise.withResolvers<void>(); - const w = new Worker( - resolveWorker("test_worker.ts"), - { type: "module", name: "tsWorker" }, - ); - const arr: number[] = []; - w.addEventListener("message", () => arr.push(1)); - w.onmessage = (_e) => { - arr.push(2); - }; - w.addEventListener("message", () => arr.push(3)); - w.addEventListener("message", () => { - resolve(); - }); - w.postMessage("Hello World"); - await promise; - assertEquals(arr, [1, 2, 3]); - w.terminate(); - }, -}); - -Deno.test({ - name: "Worker immediate close", - fn: async function () { - const { promise, resolve } = Promise.withResolvers<void>(); - const w = new Worker( - resolveWorker("immediately_close_worker.js"), - { type: "module" }, - ); - setTimeout(() => { - resolve(); - }, 1000); - await promise; - w.terminate(); - }, -}); - -Deno.test({ - name: "Worker post undefined", - fn: async function () { - const { promise, resolve } = Promise.withResolvers<void>(); - const worker = new Worker( - resolveWorker("post_undefined.ts"), - { type: "module" }, - ); - - const handleWorkerMessage = (e: MessageEvent) => { - console.log("main <- worker:", e.data); - worker.terminate(); - resolve(); - }; - - worker.addEventListener("messageerror", () => console.log("message error")); - worker.addEventListener("error", () => console.log("error")); - worker.addEventListener("message", handleWorkerMessage); - - console.log("\npost from parent"); - worker.postMessage(undefined); - await promise; - }, -}); - -Deno.test("Worker inherits permissions", async function () { - const worker = new Worker( - resolveWorker("read_check_worker.js"), - { type: "module", deno: { permissions: "inherit" } }, - ); - - const { promise, resolve } = Promise.withResolvers<boolean>(); - worker.onmessage = (e) => { - resolve(e.data); - }; - - worker.postMessage(null); - assertEquals(await promise, true); - worker.terminate(); -}); - -Deno.test("Worker limit children permissions", async function () { - const worker = new Worker( - resolveWorker("read_check_worker.js"), - { type: "module", deno: { permissions: { read: false } } }, - ); - - const { promise, resolve } = Promise.withResolvers<boolean>(); - worker.onmessage = (e) => { - resolve(e.data); - }; - - worker.postMessage(null); - assertEquals(await promise, false); - worker.terminate(); -}); - -Deno.test("Worker limit children permissions granularly", async function () { - const workerUrl = resolveWorker("read_check_granular_worker.js"); - const worker = new Worker( - workerUrl, - { - type: "module", - deno: { - permissions: { - env: ["foo"], - hrtime: true, - net: ["foo", "bar:8000"], - ffi: [new URL("foo", workerUrl), "bar"], - read: [new URL("foo", workerUrl), "bar"], - run: [new URL("foo", workerUrl), "bar", "./baz"], - write: [new URL("foo", workerUrl), "bar"], - }, - }, - }, - ); - // deno-lint-ignore no-explicit-any - const { promise, resolve } = Promise.withResolvers<any>(); - worker.onmessage = ({ data }) => resolve(data); - assertEquals(await promise, { - envGlobal: "prompt", - envFoo: "granted", - envAbsent: "prompt", - hrtime: "granted", - netGlobal: "prompt", - netFoo: "granted", - netFoo8000: "granted", - netBar: "prompt", - netBar8000: "granted", - ffiGlobal: "prompt", - ffiFoo: "granted", - ffiBar: "granted", - ffiAbsent: "prompt", - readGlobal: "prompt", - readFoo: "granted", - readBar: "granted", - readAbsent: "prompt", - runGlobal: "prompt", - runFoo: "granted", - runBar: "granted", - runBaz: "granted", - runAbsent: "prompt", - writeGlobal: "prompt", - writeFoo: "granted", - writeBar: "granted", - writeAbsent: "prompt", - }); - worker.terminate(); -}); - -Deno.test("Nested worker limit children permissions", async function () { - /** This worker has permissions but doesn't grant them to its children */ - const worker = new Worker( - resolveWorker("parent_read_check_worker.js"), - { type: "module", deno: { permissions: "inherit" } }, - ); - // deno-lint-ignore no-explicit-any - const { promise, resolve } = Promise.withResolvers<any>(); - worker.onmessage = ({ data }) => resolve(data); - assertEquals(await promise, { - envGlobal: "prompt", - envFoo: "prompt", - envAbsent: "prompt", - hrtime: "prompt", - netGlobal: "prompt", - netFoo: "prompt", - netFoo8000: "prompt", - netBar: "prompt", - netBar8000: "prompt", - ffiGlobal: "prompt", - ffiFoo: "prompt", - ffiBar: "prompt", - ffiAbsent: "prompt", - readGlobal: "prompt", - readFoo: "prompt", - readBar: "prompt", - readAbsent: "prompt", - runGlobal: "prompt", - runFoo: "prompt", - runBar: "prompt", - runBaz: "prompt", - runAbsent: "prompt", - writeGlobal: "prompt", - writeFoo: "prompt", - writeBar: "prompt", - writeAbsent: "prompt", - }); - worker.terminate(); -}); - -// This test relies on env permissions not being granted on main thread -Deno.test({ - name: - "Worker initialization throws on worker permissions greater than parent thread permissions", - permissions: { env: false }, - fn: function () { - assertThrows( - () => { - const worker = new Worker( - resolveWorker("deno_worker.ts"), - { type: "module", deno: { permissions: { env: true } } }, - ); - worker.terminate(); - }, - Deno.errors.PermissionDenied, - "Can't escalate parent thread permissions", - ); - }, -}); - -Deno.test("Worker with disabled permissions", async function () { - const worker = new Worker( - resolveWorker("no_permissions_worker.js"), - { type: "module", deno: { permissions: "none" } }, - ); - - const { promise, resolve } = Promise.withResolvers<boolean>(); - worker.onmessage = (e) => { - resolve(e.data); - }; - - worker.postMessage(null); - assertEquals(await promise, true); - worker.terminate(); -}); - -Deno.test("Worker permissions are not inherited with empty permission object", async function () { - const worker = new Worker( - resolveWorker("permission_echo.js"), - { type: "module", deno: { permissions: {} } }, - ); - - // deno-lint-ignore no-explicit-any - const { promise, resolve } = Promise.withResolvers<any>(); - worker.onmessage = (e) => { - resolve(e.data); - }; - - worker.postMessage(null); - assertEquals(await promise, { - env: "prompt", - hrtime: "prompt", - net: "prompt", - ffi: "prompt", - read: "prompt", - run: "prompt", - write: "prompt", - }); - worker.terminate(); -}); - -Deno.test("Worker permissions are not inherited with single specified permission", async function () { - const worker = new Worker( - resolveWorker("permission_echo.js"), - { type: "module", deno: { permissions: { net: true } } }, - ); - - // deno-lint-ignore no-explicit-any - const { promise, resolve } = Promise.withResolvers<any>(); - worker.onmessage = (e) => { - resolve(e.data); - }; - - worker.postMessage(null); - assertEquals(await promise, { - env: "prompt", - hrtime: "prompt", - net: "granted", - ffi: "prompt", - read: "prompt", - run: "prompt", - write: "prompt", - }); - worker.terminate(); -}); - -Deno.test("Worker with invalid permission arg", function () { - assertThrows( - () => - new Worker(`data:,close();`, { - type: "module", - // @ts-expect-error invalid env value - deno: { permissions: { env: "foo" } }, - }), - TypeError, - '(deno.permissions.env) invalid value: string "foo", expected "inherit" or boolean or string[]', - ); -}); - -Deno.test({ - name: "worker location", - fn: async function () { - const { promise, resolve } = Promise.withResolvers<string>(); - const workerModuleHref = resolveWorker("worker_location.ts"); - const w = new Worker(workerModuleHref, { type: "module" }); - w.onmessage = (e) => { - resolve(e.data); - }; - w.postMessage("Hello, world!"); - assertEquals(await promise, `${workerModuleHref}, true`); - w.terminate(); - }, -}); - -Deno.test({ - name: "Worker with top-level-await", - fn: async function () { - const { promise, resolve, reject } = Promise.withResolvers<void>(); - const worker = new Worker( - resolveWorker("worker_with_top_level_await.ts"), - { type: "module" }, - ); - worker.onmessage = (e) => { - if (e.data == "ready") { - worker.postMessage("trigger worker handler"); - } else if (e.data == "triggered worker handler") { - resolve(); - } else { - reject(new Error("Handler didn't run during top-level delay.")); - } - }; - await promise; - worker.terminate(); - }, -}); - -Deno.test({ - name: "Worker with native HTTP", - fn: async function () { - const { promise, resolve } = Promise.withResolvers<void>(); - const worker = new Worker( - resolveWorker("http_worker.js"), - { type: "module", deno: { permissions: "inherit" } }, - ); - worker.onmessage = () => { - resolve(); - }; - await promise; - - assert(worker); - const response = await fetch("http://localhost:4506"); - assert(await response.arrayBuffer()); - worker.terminate(); - }, -}); - -Deno.test({ - name: "structured cloning postMessage", - fn: async function () { - const worker = new Worker( - resolveWorker("worker_structured_cloning.ts"), - { type: "module" }, - ); - - // deno-lint-ignore no-explicit-any - const { promise, resolve } = Promise.withResolvers<any>(); - worker.onmessage = (e) => { - resolve(e.data); - }; - - worker.postMessage("START"); - const data = await promise; - // self field should reference itself (circular ref) - assert(data === data.self); - // fields a and b refer to the same array - assertEquals(data.a, ["a", true, 432]); - assertEquals(data.b, ["a", true, 432]); - data.b[0] = "b"; - data.a[2] += 5; - assertEquals(data.a, ["b", true, 437]); - assertEquals(data.b, ["b", true, 437]); - // c is a set - const len = data.c.size; - data.c.add(1); // This value is already in the set. - data.c.add(2); - assertEquals(len + 1, data.c.size); - worker.terminate(); - }, -}); - -Deno.test({ - name: "worker with relative specifier", - fn: async function () { - assertEquals(location.href, "http://127.0.0.1:4545/"); - const w = new Worker( - "./workers/test_worker.ts", - { type: "module", name: "tsWorker" }, - ); - const { promise, resolve } = Promise.withResolvers<string>(); - w.onmessage = (e) => { - resolve(e.data); - }; - w.postMessage("Hello, world!"); - assertEquals(await promise, "Hello, world!"); - w.terminate(); - }, -}); - -Deno.test({ - name: "worker SharedArrayBuffer", - fn: async function () { - const { promise, resolve } = Promise.withResolvers<void>(); - const workerOptions: WorkerOptions = { type: "module" }; - const w = new Worker( - resolveWorker("shared_array_buffer.ts"), - workerOptions, - ); - const sab1 = new SharedArrayBuffer(1); - const sab2 = new SharedArrayBuffer(1); - const bytes1 = new Uint8Array(sab1); - const bytes2 = new Uint8Array(sab2); - assertEquals(bytes1[0], 0); - assertEquals(bytes2[0], 0); - w.onmessage = () => { - w.postMessage([sab1, sab2]); - w.onmessage = () => { - resolve(); - }; - }; - await promise; - assertEquals(bytes1[0], 1); - assertEquals(bytes2[0], 2); - w.terminate(); - }, -}); - -Deno.test({ - name: "Send MessagePorts from / to workers", - fn: async function () { - const worker = new Worker( - resolveWorker("message_port.ts"), - { type: "module" }, - ); - const channel = new MessageChannel(); - - // deno-lint-ignore no-explicit-any - const deferred1 = Promise.withResolvers<any>(); - const deferred2 = Promise.withResolvers<boolean>(); - const deferred3 = Promise.withResolvers<boolean>(); - const result = Promise.withResolvers<void>(); - worker.onmessage = (e) => { - deferred1.resolve([e.data, e.ports.length]); - const port1 = e.ports[0]; - port1.onmessage = (e) => { - deferred2.resolve(e.data); - port1.close(); - worker.postMessage("3", [channel.port1]); - }; - port1.postMessage("2"); - }; - channel.port2.onmessage = (e) => { - deferred3.resolve(e.data); - channel.port2.close(); - result.resolve(); - }; - - assertEquals(await deferred1.promise, ["1", 1]); - assertEquals(await deferred2.promise, true); - assertEquals(await deferred3.promise, true); - await result.promise; - worker.terminate(); - }, -}); - -Deno.test({ - name: "worker Deno.memoryUsage", - fn: async function () { - const w = new Worker( - /** - * Source code - * self.onmessage = function() {self.postMessage(Deno.memoryUsage())} - */ - "data:application/typescript;base64,c2VsZi5vbm1lc3NhZ2UgPSBmdW5jdGlvbigpIHtzZWxmLnBvc3RNZXNzYWdlKERlbm8ubWVtb3J5VXNhZ2UoKSl9", - { type: "module", name: "tsWorker" }, - ); - - w.postMessage(null); - - // deno-lint-ignore no-explicit-any - const { promise, resolve } = Promise.withResolvers<any>(); - w.onmessage = function (evt) { - resolve(evt.data); - }; - - assertEquals( - Object.keys( - await promise as unknown as Record<string, number>, - ), - ["rss", "heapTotal", "heapUsed", "external"], - ); - w.terminate(); - }, -}); diff --git a/cli/tests/unit/write_file_test.ts b/cli/tests/unit/write_file_test.ts deleted file mode 100644 index 6cd08e2d1..000000000 --- a/cli/tests/unit/write_file_test.ts +++ /dev/null @@ -1,427 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertRejects, - assertThrows, - unreachable, -} from "./test_util.ts"; - -Deno.test( - { permissions: { read: true, write: true } }, - function writeFileSyncSuccess() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - Deno.writeFileSync(filename, data); - const dataRead = Deno.readFileSync(filename); - const dec = new TextDecoder("utf-8"); - const actual = dec.decode(dataRead); - assertEquals(actual, "Hello"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function writeFileSyncUrl() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const tempDir = Deno.makeTempDirSync(); - const fileUrl = new URL( - `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`, - ); - Deno.writeFileSync(fileUrl, data); - const dataRead = Deno.readFileSync(fileUrl); - const dec = new TextDecoder("utf-8"); - const actual = dec.decode(dataRead); - assertEquals(actual, "Hello"); - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -Deno.test({ permissions: { write: true } }, function writeFileSyncFail() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = "/baddir/test.txt"; - // The following should fail because /baddir doesn't exist (hopefully). - assertThrows(() => { - Deno.writeFileSync(filename, data); - }, Deno.errors.NotFound); -}); - -Deno.test({ permissions: { write: false } }, function writeFileSyncPerm() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = "/baddir/test.txt"; - // The following should fail due to no write permission - assertThrows(() => { - Deno.writeFileSync(filename, data); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { permissions: { read: true, write: true } }, - function writeFileSyncUpdateMode() { - if (Deno.build.os !== "windows") { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - Deno.writeFileSync(filename, data, { mode: 0o755 }); - assertEquals(Deno.statSync(filename).mode! & 0o777, 0o755); - Deno.writeFileSync(filename, data, { mode: 0o666 }); - assertEquals(Deno.statSync(filename).mode! & 0o777, 0o666); - } - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function writeFileSyncCreate() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - // if create turned off, the file won't be created - assertThrows(() => { - Deno.writeFileSync(filename, data, { create: false }); - }, Deno.errors.NotFound); - - // Turn on create, should have no error - Deno.writeFileSync(filename, data, { create: true }); - Deno.writeFileSync(filename, data, { create: false }); - const dataRead = Deno.readFileSync(filename); - const dec = new TextDecoder("utf-8"); - const actual = dec.decode(dataRead); - assertEquals(actual, "Hello"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function writeFileSyncCreateNew() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - Deno.writeFileSync(filename, data, { createNew: true }); - - assertThrows(() => { - Deno.writeFileSync(filename, data, { createNew: true }); - }, Deno.errors.AlreadyExists); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function writeFileSyncAppend() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - Deno.writeFileSync(filename, data); - Deno.writeFileSync(filename, data, { append: true }); - let dataRead = Deno.readFileSync(filename); - const dec = new TextDecoder("utf-8"); - let actual = dec.decode(dataRead); - assertEquals(actual, "HelloHello"); - // Now attempt overwrite - Deno.writeFileSync(filename, data, { append: false }); - dataRead = Deno.readFileSync(filename); - actual = dec.decode(dataRead); - assertEquals(actual, "Hello"); - // append not set should also overwrite - Deno.writeFileSync(filename, data); - dataRead = Deno.readFileSync(filename); - actual = dec.decode(dataRead); - assertEquals(actual, "Hello"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileSuccess() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - await Deno.writeFile(filename, data); - const dataRead = Deno.readFileSync(filename); - const dec = new TextDecoder("utf-8"); - const actual = dec.decode(dataRead); - assertEquals(actual, "Hello"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileUrl() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const tempDir = await Deno.makeTempDir(); - const fileUrl = new URL( - `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`, - ); - await Deno.writeFile(fileUrl, data); - const dataRead = Deno.readFileSync(fileUrl); - const dec = new TextDecoder("utf-8"); - const actual = dec.decode(dataRead); - assertEquals(actual, "Hello"); - - Deno.removeSync(tempDir, { recursive: true }); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileNotFound() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = "/baddir/test.txt"; - // The following should fail because /baddir doesn't exist (hopefully). - await assertRejects(async () => { - await Deno.writeFile(filename, data); - }, Deno.errors.NotFound); - }, -); - -Deno.test( - { permissions: { read: true, write: false } }, - async function writeFilePerm() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = "/baddir/test.txt"; - // The following should fail due to no write permission - await assertRejects(async () => { - await Deno.writeFile(filename, data); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileUpdateMode() { - if (Deno.build.os !== "windows") { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - await Deno.writeFile(filename, data, { mode: 0o755 }); - assertEquals(Deno.statSync(filename).mode! & 0o777, 0o755); - await Deno.writeFile(filename, data, { mode: 0o666 }); - assertEquals(Deno.statSync(filename).mode! & 0o777, 0o666); - } - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileCreate() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - // if create turned off, the file won't be created - await assertRejects(async () => { - await Deno.writeFile(filename, data, { create: false }); - }, Deno.errors.NotFound); - - // Turn on create, should have no error - await Deno.writeFile(filename, data, { create: true }); - await Deno.writeFile(filename, data, { create: false }); - const dataRead = Deno.readFileSync(filename); - const dec = new TextDecoder("utf-8"); - const actual = dec.decode(dataRead); - assertEquals(actual, "Hello"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileCreateNew() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - await Deno.writeFile(filename, data, { createNew: true }); - await assertRejects(async () => { - await Deno.writeFile(filename, data, { createNew: true }); - }, Deno.errors.AlreadyExists); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileAppend() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - await Deno.writeFile(filename, data); - await Deno.writeFile(filename, data, { append: true }); - let dataRead = Deno.readFileSync(filename); - const dec = new TextDecoder("utf-8"); - let actual = dec.decode(dataRead); - assertEquals(actual, "HelloHello"); - // Now attempt overwrite - await Deno.writeFile(filename, data, { append: false }); - dataRead = Deno.readFileSync(filename); - actual = dec.decode(dataRead); - assertEquals(actual, "Hello"); - // append not set should also overwrite - await Deno.writeFile(filename, data); - dataRead = Deno.readFileSync(filename); - actual = dec.decode(dataRead); - assertEquals(actual, "Hello"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileAbortSignal(): Promise<void> { - const ac = new AbortController(); - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - queueMicrotask(() => ac.abort()); - try { - await Deno.writeFile(filename, data, { signal: ac.signal }); - unreachable(); - } catch (e) { - assert(e instanceof Error); - assertEquals(e.name, "AbortError"); - } - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileAbortSignalReason(): Promise<void> { - const ac = new AbortController(); - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - const abortReason = new Error(); - queueMicrotask(() => ac.abort(abortReason)); - try { - await Deno.writeFile(filename, data, { signal: ac.signal }); - unreachable(); - } catch (e) { - assertEquals(e, abortReason); - } - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileAbortSignalPrimitiveReason(): Promise<void> { - const ac = new AbortController(); - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - queueMicrotask(() => ac.abort("Some string")); - try { - await Deno.writeFile(filename, data, { signal: ac.signal }); - unreachable(); - } catch (e) { - assertEquals(e, "Some string"); - } - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileAbortSignalPreAborted(): Promise<void> { - const ac = new AbortController(); - ac.abort(); - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - try { - await Deno.writeFile(filename, data, { signal: ac.signal }); - unreachable(); - } catch (e) { - assert(e instanceof Error); - assertEquals(e.name, "AbortError"); - } - assertNotExists(filename); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileAbortSignalReasonPreAborted(): Promise<void> { - const ac = new AbortController(); - const abortReason = new Error(); - ac.abort(abortReason); - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - try { - await Deno.writeFile(filename, data, { signal: ac.signal }); - unreachable(); - } catch (e) { - assertEquals(e, abortReason); - } - assertNotExists(filename); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileAbortSignalPrimitiveReasonPreAborted(): Promise< - void - > { - const ac = new AbortController(); - ac.abort("Some string"); - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - try { - await Deno.writeFile(filename, data, { signal: ac.signal }); - unreachable(); - } catch (e) { - assertEquals(e, "Some string"); - } - assertNotExists(filename); - }, -); - -// Test that AbortController's cancel handle is cleaned-up correctly, and do not leak resources. -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileWithAbortSignalNotCalled() { - const ac = new AbortController(); - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - await Deno.writeFile(filename, data, { signal: ac.signal }); - const dataRead = Deno.readFileSync(filename); - const dec = new TextDecoder("utf-8"); - const actual = dec.decode(dataRead); - assertEquals(actual, "Hello"); - }, -); - -function assertNotExists(filename: string | URL) { - if (pathExists(filename)) { - throw new Error(`The file ${filename} exists.`); - } -} - -function pathExists(path: string | URL) { - try { - Deno.statSync(path); - return true; - } catch { - return false; - } -} - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeFileStream() { - const stream = new ReadableStream({ - pull(controller) { - controller.enqueue(new Uint8Array([1])); - controller.enqueue(new Uint8Array([2])); - controller.close(); - }, - }); - const filename = Deno.makeTempDirSync() + "/test.txt"; - await Deno.writeFile(filename, stream); - assertEquals(Deno.readFileSync(filename), new Uint8Array([1, 2])); - }, -); diff --git a/cli/tests/unit/write_text_file_test.ts b/cli/tests/unit/write_text_file_test.ts deleted file mode 100644 index a58d91997..000000000 --- a/cli/tests/unit/write_text_file_test.ts +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { - assert, - assertEquals, - assertRejects, - assertThrows, -} from "./test_util.ts"; - -Deno.test( - { permissions: { read: true, write: true } }, - function writeTextFileSyncSuccess() { - const filename = Deno.makeTempDirSync() + "/test.txt"; - Deno.writeTextFileSync(filename, "Hello"); - const dataRead = Deno.readTextFileSync(filename); - assertEquals(dataRead, "Hello"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function writeTextFileSyncByUrl() { - const tempDir = Deno.makeTempDirSync(); - const fileUrl = new URL( - `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`, - ); - Deno.writeTextFileSync(fileUrl, "Hello"); - const dataRead = Deno.readTextFileSync(fileUrl); - assertEquals(dataRead, "Hello"); - - Deno.removeSync(fileUrl, { recursive: true }); - }, -); - -Deno.test({ permissions: { write: true } }, function writeTextFileSyncFail() { - const filename = "/baddir/test.txt"; - // The following should fail because /baddir doesn't exist (hopefully). - assertThrows(() => { - Deno.writeTextFileSync(filename, "hello"); - }, Deno.errors.NotFound); -}); - -Deno.test({ permissions: { write: false } }, function writeTextFileSyncPerm() { - const filename = "/baddir/test.txt"; - // The following should fail due to no write permission - assertThrows(() => { - Deno.writeTextFileSync(filename, "Hello"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { permissions: { read: true, write: true } }, - function writeTextFileSyncUpdateMode() { - if (Deno.build.os !== "windows") { - const data = "Hello"; - const filename = Deno.makeTempDirSync() + "/test.txt"; - Deno.writeTextFileSync(filename, data, { mode: 0o755 }); - assertEquals(Deno.statSync(filename).mode! & 0o777, 0o755); - Deno.writeTextFileSync(filename, data, { mode: 0o666 }); - assertEquals(Deno.statSync(filename).mode! & 0o777, 0o666); - } - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function writeTextFileSyncCreate() { - const data = "Hello"; - const filename = Deno.makeTempDirSync() + "/test.txt"; - let caughtError = false; - // if create turned off, the file won't be created - try { - Deno.writeTextFileSync(filename, data, { create: false }); - } catch (e) { - caughtError = true; - assert(e instanceof Deno.errors.NotFound); - } - assert(caughtError); - - // Turn on create, should have no error - Deno.writeTextFileSync(filename, data, { create: true }); - Deno.writeTextFileSync(filename, data, { create: false }); - assertEquals(Deno.readTextFileSync(filename), "Hello"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - function writeTextFileSyncAppend() { - const data = "Hello"; - const filename = Deno.makeTempDirSync() + "/test.txt"; - Deno.writeTextFileSync(filename, data); - Deno.writeTextFileSync(filename, data, { append: true }); - assertEquals(Deno.readTextFileSync(filename), "HelloHello"); - // Now attempt overwrite - Deno.writeTextFileSync(filename, data, { append: false }); - assertEquals(Deno.readTextFileSync(filename), "Hello"); - // append not set should also overwrite - Deno.writeTextFileSync(filename, data); - assertEquals(Deno.readTextFileSync(filename), "Hello"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeTextFileSuccess() { - const filename = Deno.makeTempDirSync() + "/test.txt"; - await Deno.writeTextFile(filename, "Hello"); - const dataRead = Deno.readTextFileSync(filename); - assertEquals(dataRead, "Hello"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeTextFileByUrl() { - const tempDir = Deno.makeTempDirSync(); - const fileUrl = new URL( - `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`, - ); - await Deno.writeTextFile(fileUrl, "Hello"); - const dataRead = Deno.readTextFileSync(fileUrl); - assertEquals(dataRead, "Hello"); - - Deno.removeSync(fileUrl, { recursive: true }); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeTextFileNotFound() { - const filename = "/baddir/test.txt"; - // The following should fail because /baddir doesn't exist (hopefully). - await assertRejects(async () => { - await Deno.writeTextFile(filename, "Hello"); - }, Deno.errors.NotFound); - }, -); - -Deno.test( - { permissions: { write: false } }, - async function writeTextFilePerm() { - const filename = "/baddir/test.txt"; - // The following should fail due to no write permission - await assertRejects(async () => { - await Deno.writeTextFile(filename, "Hello"); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeTextFileUpdateMode() { - if (Deno.build.os !== "windows") { - const data = "Hello"; - const filename = Deno.makeTempDirSync() + "/test.txt"; - await Deno.writeTextFile(filename, data, { mode: 0o755 }); - assertEquals(Deno.statSync(filename).mode! & 0o777, 0o755); - await Deno.writeTextFile(filename, data, { mode: 0o666 }); - assertEquals(Deno.statSync(filename).mode! & 0o777, 0o666); - } - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeTextFileCreate() { - const data = "Hello"; - const filename = Deno.makeTempDirSync() + "/test.txt"; - let caughtError = false; - // if create turned off, the file won't be created - try { - await Deno.writeTextFile(filename, data, { create: false }); - } catch (e) { - caughtError = true; - assert(e instanceof Deno.errors.NotFound); - } - assert(caughtError); - - // Turn on create, should have no error - await Deno.writeTextFile(filename, data, { create: true }); - await Deno.writeTextFile(filename, data, { create: false }); - assertEquals(Deno.readTextFileSync(filename), "Hello"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeTextFileAppend() { - const data = "Hello"; - const filename = Deno.makeTempDirSync() + "/test.txt"; - await Deno.writeTextFile(filename, data); - await Deno.writeTextFile(filename, data, { append: true }); - assertEquals(Deno.readTextFileSync(filename), "HelloHello"); - // Now attempt overwrite - await Deno.writeTextFile(filename, data, { append: false }); - assertEquals(Deno.readTextFileSync(filename), "Hello"); - // append not set should also overwrite - await Deno.writeTextFile(filename, data); - assertEquals(Deno.readTextFileSync(filename), "Hello"); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function writeTextFileStream() { - const stream = new ReadableStream({ - pull(controller) { - controller.enqueue("Hello"); - controller.enqueue("World"); - controller.close(); - }, - }); - const filename = Deno.makeTempDirSync() + "/test.txt"; - await Deno.writeTextFile(filename, stream); - assertEquals(Deno.readTextFileSync(filename), "HelloWorld"); - }, -); |