summaryrefslogtreecommitdiff
path: root/cli/tests/unit
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tests/unit')
-rw-r--r--cli/tests/unit/request_test.ts2
-rw-r--r--cli/tests/unit/websocket_test.ts301
-rw-r--r--cli/tests/unit/websocketstream_test.ts353
-rw-r--r--cli/tests/unit/worker_test.ts843
-rw-r--r--cli/tests/unit/worker_types.ts14
5 files changed, 1498 insertions, 15 deletions
diff --git a/cli/tests/unit/request_test.ts b/cli/tests/unit/request_test.ts
index 73a24304e..fe34c20a5 100644
--- a/cli/tests/unit/request_test.ts
+++ b/cli/tests/unit/request_test.ts
@@ -33,7 +33,7 @@ Deno.test(function methodNonString() {
Deno.test(function requestRelativeUrl() {
assertEquals(
new Request("relative-url").url,
- "http://js-unit-tests/foo/relative-url",
+ "http://127.0.0.1:4545/relative-url",
);
});
diff --git a/cli/tests/unit/websocket_test.ts b/cli/tests/unit/websocket_test.ts
index 6a1dc3525..42681c187 100644
--- a/cli/tests/unit/websocket_test.ts
+++ b/cli/tests/unit/websocket_test.ts
@@ -435,3 +435,304 @@ Deno.test(
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 b/cli/tests/unit/websocketstream_test.ts
new file mode 100644
index 000000000..0a16f254e
--- /dev/null
+++ b/cli/tests/unit/websocketstream_test.ts
@@ -0,0 +1,353 @@
+// 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", 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();
+ },
+);
+
+Deno.test("async close with empty stream", 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();
+});
+
+Deno.test("async close with unread messages in stream", 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/worker_test.ts b/cli/tests/unit/worker_test.ts
new file mode 100644
index 000000000..eea0e8106
--- /dev/null
+++ b/cli/tests/unit/worker_test.ts
@@ -0,0 +1,843 @@
+// 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/worker_types.ts b/cli/tests/unit/worker_types.ts
deleted file mode 100644
index cb71418a4..000000000
--- a/cli/tests/unit/worker_types.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-import { assert } from "./test_util.ts";
-
-Deno.test(
- { permissions: { read: true } },
- function utimeSyncFileSuccess() {
- const w = new Worker(
- import.meta.resolve("../testdata/workers/worker_types.ts"),
- { type: "module" },
- );
- assert(w);
- w.terminate();
- },
-);