2020-02-11 04:04:59 -05:00
|
|
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
2020-03-03 12:22:53 -05:00
|
|
|
|
|
|
|
// Requires to be run with `--allow-net` flag
|
|
|
|
|
|
|
|
// FIXME(bartlomieju): this file is an integration test only because
|
|
|
|
// workers are leaking ops at the moment - `worker.terminate()` is not
|
|
|
|
// yet implemented. Once it gets implemented this file should be
|
|
|
|
// again moved to `cli/js/` as an unit test file.
|
|
|
|
|
|
|
|
import { assert, assertEquals } from "../../std/testing/asserts.ts";
|
|
|
|
|
|
|
|
export interface ResolvableMethods<T> {
|
|
|
|
resolve: (value?: T | PromiseLike<T>) => void;
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
reject: (reason?: any) => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export type Resolvable<T> = Promise<T> & ResolvableMethods<T>;
|
|
|
|
|
|
|
|
export function createResolvable<T>(): Resolvable<T> {
|
|
|
|
let methods: ResolvableMethods<T>;
|
|
|
|
const promise = new Promise<T>((resolve, reject): void => {
|
|
|
|
methods = { resolve, reject };
|
|
|
|
});
|
|
|
|
// TypeScript doesn't know that the Promise callback occurs synchronously
|
|
|
|
// therefore use of not null assertion (`!`)
|
|
|
|
return Object.assign(promise, methods!) as Resolvable<T>;
|
|
|
|
}
|
|
|
|
|
2020-03-18 19:25:55 -04:00
|
|
|
Deno.test({
|
2020-04-09 18:15:17 -04:00
|
|
|
name: "worker terminate",
|
2020-03-28 13:03:49 -04:00
|
|
|
fn: async function (): Promise<void> {
|
2020-03-18 19:25:55 -04:00
|
|
|
const promise = createResolvable();
|
2020-04-09 18:15:17 -04:00
|
|
|
|
2020-06-09 08:33:52 -04:00
|
|
|
const jsWorker = new Worker(
|
|
|
|
new URL("subdir/test_worker.js", import.meta.url).href,
|
2020-07-14 15:24:17 -04:00
|
|
|
{ type: "module" },
|
2020-06-09 08:33:52 -04:00
|
|
|
);
|
|
|
|
const tsWorker = new Worker(
|
|
|
|
new URL("subdir/test_worker.ts", import.meta.url).href,
|
2020-07-14 15:24:17 -04:00
|
|
|
{ type: "module", name: "tsWorker" },
|
2020-06-09 08:33:52 -04:00
|
|
|
);
|
2020-03-18 19:25:55 -04:00
|
|
|
|
|
|
|
tsWorker.onmessage = (e): void => {
|
|
|
|
assertEquals(e.data, "Hello World");
|
|
|
|
promise.resolve();
|
|
|
|
};
|
|
|
|
|
|
|
|
jsWorker.onmessage = (e): void => {
|
|
|
|
assertEquals(e.data, "Hello World");
|
|
|
|
tsWorker.postMessage("Hello World");
|
|
|
|
};
|
|
|
|
|
|
|
|
jsWorker.onerror = (e: Event): void => {
|
|
|
|
e.preventDefault();
|
|
|
|
jsWorker.postMessage("Hello World");
|
|
|
|
};
|
2020-02-11 04:04:59 -05:00
|
|
|
|
|
|
|
jsWorker.postMessage("Hello World");
|
2020-03-18 19:25:55 -04:00
|
|
|
await promise;
|
2020-04-09 18:15:17 -04:00
|
|
|
tsWorker.terminate();
|
|
|
|
jsWorker.terminate();
|
2020-03-28 13:03:49 -04:00
|
|
|
},
|
2020-02-11 04:04:59 -05:00
|
|
|
});
|
|
|
|
|
2020-03-18 19:25:55 -04:00
|
|
|
Deno.test({
|
2020-04-09 18:15:17 -04:00
|
|
|
name: "worker nested",
|
2020-03-28 13:03:49 -04:00
|
|
|
fn: async function (): Promise<void> {
|
2020-03-18 19:25:55 -04:00
|
|
|
const promise = createResolvable();
|
|
|
|
|
2020-06-09 08:33:52 -04:00
|
|
|
const nestedWorker = new Worker(
|
|
|
|
new URL("subdir/nested_worker.js", import.meta.url).href,
|
2020-07-14 15:24:17 -04:00
|
|
|
{ type: "module", name: "nested" },
|
2020-06-09 08:33:52 -04:00
|
|
|
);
|
2020-03-18 19:25:55 -04:00
|
|
|
|
|
|
|
nestedWorker.onmessage = (e): void => {
|
|
|
|
assert(e.data.type !== "error");
|
|
|
|
promise.resolve();
|
|
|
|
};
|
|
|
|
|
|
|
|
nestedWorker.postMessage("Hello World");
|
|
|
|
await promise;
|
2020-04-09 18:15:17 -04:00
|
|
|
nestedWorker.terminate();
|
2020-03-28 13:03:49 -04:00
|
|
|
},
|
2020-02-11 04:04:59 -05:00
|
|
|
});
|
|
|
|
|
2020-03-18 19:25:55 -04:00
|
|
|
Deno.test({
|
2020-04-09 18:15:17 -04:00
|
|
|
name: "worker throws when executing",
|
2020-03-28 13:03:49 -04:00
|
|
|
fn: async function (): Promise<void> {
|
2020-03-18 19:25:55 -04:00
|
|
|
const promise = createResolvable();
|
2020-06-09 08:33:52 -04:00
|
|
|
const throwingWorker = new Worker(
|
|
|
|
new URL("subdir/throwing_worker.js", import.meta.url).href,
|
2020-07-14 15:24:17 -04:00
|
|
|
{ type: "module" },
|
2020-06-09 08:33:52 -04:00
|
|
|
);
|
2020-03-18 19:25:55 -04:00
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
throwingWorker.onerror = (e: any): void => {
|
|
|
|
e.preventDefault();
|
|
|
|
assert(/Uncaught Error: Thrown error/.test(e.message));
|
|
|
|
promise.resolve();
|
|
|
|
};
|
|
|
|
|
|
|
|
await promise;
|
2020-04-09 18:15:17 -04:00
|
|
|
throwingWorker.terminate();
|
2020-03-28 13:03:49 -04:00
|
|
|
},
|
2020-02-11 04:04:59 -05:00
|
|
|
});
|
2020-02-21 10:35:41 -05:00
|
|
|
|
2020-03-18 19:25:55 -04:00
|
|
|
Deno.test({
|
2020-04-09 18:15:17 -04:00
|
|
|
name: "worker fetch API",
|
2020-03-28 13:03:49 -04:00
|
|
|
fn: async function (): Promise<void> {
|
2020-03-18 19:25:55 -04:00
|
|
|
const promise = createResolvable();
|
|
|
|
|
2020-06-09 08:33:52 -04:00
|
|
|
const fetchingWorker = new Worker(
|
|
|
|
new URL("subdir/fetching_worker.js", import.meta.url).href,
|
2020-07-14 15:24:17 -04:00
|
|
|
{ type: "module" },
|
2020-06-09 08:33:52 -04:00
|
|
|
);
|
2020-03-18 19:25:55 -04:00
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
fetchingWorker.onerror = (e: any): void => {
|
|
|
|
e.preventDefault();
|
|
|
|
promise.reject(e.message);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Defer promise.resolve() to allow worker to shut down
|
|
|
|
fetchingWorker.onmessage = (e): void => {
|
|
|
|
assert(e.data === "Done!");
|
|
|
|
promise.resolve();
|
|
|
|
};
|
|
|
|
|
2020-04-09 18:15:17 -04:00
|
|
|
await promise;
|
|
|
|
fetchingWorker.terminate();
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test({
|
|
|
|
name: "worker terminate busy loop",
|
|
|
|
fn: async function (): Promise<void> {
|
|
|
|
const promise = createResolvable();
|
|
|
|
|
2020-06-09 08:33:52 -04:00
|
|
|
const busyWorker = new Worker(
|
|
|
|
new URL("subdir/busy_worker.js", import.meta.url).href,
|
2020-07-14 15:24:17 -04:00
|
|
|
{ type: "module" },
|
2020-06-09 08:33:52 -04:00
|
|
|
);
|
2020-04-09 18:15:17 -04:00
|
|
|
|
|
|
|
let testResult = 0;
|
|
|
|
|
|
|
|
busyWorker.onmessage = (e): void => {
|
|
|
|
testResult = e.data;
|
|
|
|
if (testResult >= 10000) {
|
|
|
|
busyWorker.terminate();
|
|
|
|
busyWorker.onmessage = (_e): void => {
|
|
|
|
throw new Error("unreachable");
|
|
|
|
};
|
|
|
|
setTimeout(() => {
|
|
|
|
assertEquals(testResult, 10000);
|
|
|
|
promise.resolve();
|
|
|
|
}, 100);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
busyWorker.postMessage("ping");
|
|
|
|
await promise;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test({
|
|
|
|
name: "worker race condition",
|
|
|
|
fn: async function (): Promise<void> {
|
|
|
|
// See issue for details
|
|
|
|
// https://github.com/denoland/deno/issues/4080
|
|
|
|
const promise = createResolvable();
|
|
|
|
|
2020-06-09 08:33:52 -04:00
|
|
|
const racyWorker = new Worker(
|
|
|
|
new URL("subdir/racy_worker.js", import.meta.url).href,
|
2020-07-14 15:24:17 -04:00
|
|
|
{ type: "module" },
|
2020-06-09 08:33:52 -04:00
|
|
|
);
|
2020-04-09 18:15:17 -04:00
|
|
|
|
|
|
|
racyWorker.onmessage = (e): void => {
|
|
|
|
assertEquals(e.data.buf.length, 999999);
|
|
|
|
racyWorker.onmessage = (_e): void => {
|
|
|
|
throw new Error("unreachable");
|
|
|
|
};
|
|
|
|
setTimeout(() => {
|
|
|
|
promise.resolve();
|
|
|
|
}, 100);
|
|
|
|
};
|
|
|
|
|
2020-03-18 19:25:55 -04:00
|
|
|
await promise;
|
2020-03-28 13:03:49 -04:00
|
|
|
},
|
2020-02-21 10:35:41 -05:00
|
|
|
});
|
2020-04-13 12:34:32 -04:00
|
|
|
|
|
|
|
Deno.test({
|
|
|
|
name: "worker is event listener",
|
|
|
|
fn: async function (): Promise<void> {
|
|
|
|
let messageHandlersCalled = 0;
|
|
|
|
let errorHandlersCalled = 0;
|
|
|
|
|
|
|
|
const promise1 = createResolvable();
|
|
|
|
const promise2 = createResolvable();
|
|
|
|
|
2020-06-09 08:33:52 -04:00
|
|
|
const worker = new Worker(
|
|
|
|
new URL("subdir/event_worker.js", import.meta.url).href,
|
2020-07-14 15:24:17 -04:00
|
|
|
{ type: "module" },
|
2020-06-09 08:33:52 -04:00
|
|
|
);
|
2020-04-13 12:34:32 -04:00
|
|
|
|
|
|
|
worker.onmessage = (_e: Event): void => {
|
|
|
|
messageHandlersCalled++;
|
|
|
|
};
|
|
|
|
worker.addEventListener("message", (_e: Event) => {
|
|
|
|
messageHandlersCalled++;
|
|
|
|
});
|
|
|
|
worker.addEventListener("message", (_e: Event) => {
|
|
|
|
messageHandlersCalled++;
|
|
|
|
promise1.resolve();
|
|
|
|
});
|
|
|
|
|
|
|
|
worker.onerror = (e): void => {
|
|
|
|
errorHandlersCalled++;
|
|
|
|
e.preventDefault();
|
|
|
|
};
|
|
|
|
worker.addEventListener("error", (_e: Event) => {
|
|
|
|
errorHandlersCalled++;
|
|
|
|
});
|
|
|
|
worker.addEventListener("error", (_e: Event) => {
|
|
|
|
errorHandlersCalled++;
|
|
|
|
promise2.resolve();
|
|
|
|
});
|
|
|
|
|
|
|
|
worker.postMessage("ping");
|
|
|
|
await promise1;
|
|
|
|
assertEquals(messageHandlersCalled, 3);
|
|
|
|
|
|
|
|
worker.postMessage("boom");
|
|
|
|
await promise2;
|
|
|
|
assertEquals(errorHandlersCalled, 3);
|
|
|
|
worker.terminate();
|
|
|
|
},
|
|
|
|
});
|
2020-04-13 16:18:31 -04:00
|
|
|
|
|
|
|
Deno.test({
|
|
|
|
name: "worker scope is event listener",
|
|
|
|
fn: async function (): Promise<void> {
|
|
|
|
const promise1 = createResolvable();
|
|
|
|
|
2020-06-09 08:33:52 -04:00
|
|
|
const worker = new Worker(
|
|
|
|
new URL("subdir/event_worker_scope.js", import.meta.url).href,
|
2020-07-14 15:24:17 -04:00
|
|
|
{ type: "module" },
|
2020-06-09 08:33:52 -04:00
|
|
|
);
|
2020-04-13 16:18:31 -04:00
|
|
|
|
|
|
|
worker.onmessage = (e: MessageEvent): void => {
|
|
|
|
const { messageHandlersCalled, errorHandlersCalled } = e.data;
|
|
|
|
assertEquals(messageHandlersCalled, 4);
|
|
|
|
assertEquals(errorHandlersCalled, 4);
|
|
|
|
promise1.resolve();
|
|
|
|
};
|
|
|
|
|
|
|
|
worker.onerror = (_e): void => {
|
|
|
|
throw new Error("unreachable");
|
|
|
|
};
|
|
|
|
|
|
|
|
worker.postMessage("boom");
|
|
|
|
worker.postMessage("ping");
|
|
|
|
await promise1;
|
|
|
|
worker.terminate();
|
|
|
|
},
|
|
|
|
});
|
2020-04-16 17:40:29 -04:00
|
|
|
|
|
|
|
Deno.test({
|
|
|
|
name: "worker with Deno namespace",
|
|
|
|
fn: async function (): Promise<void> {
|
|
|
|
const promise = createResolvable();
|
|
|
|
const promise2 = createResolvable();
|
|
|
|
|
2020-06-09 08:33:52 -04:00
|
|
|
const regularWorker = new Worker(
|
|
|
|
new URL("subdir/non_deno_worker.js", import.meta.url).href,
|
2020-07-14 15:24:17 -04:00
|
|
|
{ type: "module" },
|
2020-06-09 08:33:52 -04:00
|
|
|
);
|
|
|
|
const denoWorker = new Worker(
|
|
|
|
new URL("subdir/deno_worker.ts", import.meta.url).href,
|
2020-07-14 15:24:17 -04:00
|
|
|
{ type: "module", deno: true },
|
2020-06-09 08:33:52 -04:00
|
|
|
);
|
2020-04-16 17:40:29 -04:00
|
|
|
|
|
|
|
regularWorker.onmessage = (e): void => {
|
|
|
|
assertEquals(e.data, "Hello World");
|
|
|
|
regularWorker.terminate();
|
|
|
|
promise.resolve();
|
|
|
|
};
|
|
|
|
|
|
|
|
denoWorker.onmessage = (e): void => {
|
|
|
|
assertEquals(e.data, "Hello World");
|
|
|
|
denoWorker.terminate();
|
|
|
|
promise2.resolve();
|
|
|
|
};
|
|
|
|
|
|
|
|
regularWorker.postMessage("Hello World");
|
|
|
|
await promise;
|
|
|
|
denoWorker.postMessage("Hello World");
|
|
|
|
await promise2;
|
|
|
|
},
|
|
|
|
});
|
2020-05-08 08:30:53 -04:00
|
|
|
|
|
|
|
Deno.test({
|
|
|
|
name: "worker with crypto in scope",
|
|
|
|
fn: async function (): Promise<void> {
|
|
|
|
const promise = createResolvable();
|
2020-06-09 08:33:52 -04:00
|
|
|
const w = new Worker(
|
|
|
|
new URL("subdir/worker_crypto.js", import.meta.url).href,
|
2020-07-14 15:24:17 -04:00
|
|
|
{ type: "module" },
|
2020-06-09 08:33:52 -04:00
|
|
|
);
|
2020-05-08 08:30:53 -04:00
|
|
|
w.onmessage = (e): void => {
|
|
|
|
assertEquals(e.data, true);
|
|
|
|
promise.resolve();
|
|
|
|
};
|
|
|
|
w.postMessage(null);
|
|
|
|
await promise;
|
|
|
|
w.terminate();
|
|
|
|
},
|
|
|
|
});
|