deno-vm
Version:
A VM module that provides a secure runtime environment via Deno.
1,346 lines (1,079 loc) • 41.3 kB
text/typescript
import { DenoWorker } from './DenoWorker';
import { readFileSync, unlinkSync, existsSync, mkdirSync } from 'fs';
import { randomBytes } from 'crypto';
import path from 'path';
import { URL } from 'url';
import { MessageChannel, MessagePort } from './MessageChannel';
import psList from 'ps-list';
import child_process from 'child_process';
console.log = jest.fn();
jest.setTimeout(10000);
describe('DenoWorker', () => {
let worker: DenoWorker;
const echoFile = path.resolve(__dirname, './test/echo.js');
const echoScript = readFileSync(echoFile, { encoding: 'utf-8' });
const pingFile = path.resolve(__dirname, './test/ping.js');
const pingScript = readFileSync(pingFile, { encoding: 'utf-8' });
const infiniteFile = path.resolve(__dirname, './test/infinite.js');
const infiniteScript = readFileSync(infiniteFile, { encoding: 'utf-8' });
const fetchFile = path.resolve(__dirname, './test/fetch.js');
const fetchScript = readFileSync(fetchFile, { encoding: 'utf-8' });
const failFile = path.resolve(__dirname, './test/fail.js');
const failScript = readFileSync(failFile, { encoding: 'utf-8' });
const envFile = path.resolve(__dirname, './test/env.js');
const envScript = readFileSync(envFile, { encoding: 'utf-8' });
const memoryCrashFile = path.resolve(__dirname, './test/memory.js');
const unresolvedPromiseFile = path.resolve(
__dirname,
'./test/unresolved_promise.js'
);
const unresolvedPromiseScript = readFileSync(unresolvedPromiseFile, {
encoding: 'utf-8',
});
afterEach(() => {
if (worker) {
worker.terminate();
}
});
describe('scripts', () => {
it('should be able to run the given script', async () => {
worker = new DenoWorker(echoScript);
let ret: any;
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
ret = e.data;
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
expect(ret).toEqual({
type: 'echo',
message: 'Hello',
});
});
it('should be able to import the given script', async () => {
const file = path.resolve(__dirname, './test/echo.js');
const url = new URL(`file://${file}`);
worker = new DenoWorker(url, {
permissions: {
allowRead: [file],
},
});
let ret: any;
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
ret = e.data;
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
expect(ret).toEqual({
type: 'echo',
message: 'Hello',
});
});
it('should be able to specify additional network addresses to allow', async () => {
worker = new DenoWorker(echoScript, {
permissions: {
allowNet: [`https://google.com`],
},
});
let ret: any;
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
ret = e.data;
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
expect(ret).toEqual({
type: 'echo',
message: 'Hello',
});
});
it('should be able to specify network addresses to block', async () => {
const host = `example.com`;
worker = new DenoWorker(fetchScript, {
permissions: {
allowNet: true,
denyNet: [host],
},
});
let ret: any;
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
ret = e.data;
resolve();
};
worker.postMessage({
type: 'fetch',
url: `https://${host}`,
});
await promise;
expect(ret).toMatchObject({
type: 'error',
});
});
it('should call onexit when the script fails', async () => {
worker = new DenoWorker(failScript);
let exitCode: number;
let exitSignal: string;
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onexit = (code, status) => {
exitCode = code;
exitSignal = status;
resolve();
};
let ret: any;
worker.onmessage = (e) => {
ret = e.data;
};
await promise;
expect(ret).toBeUndefined();
const isWindows = /^win/.test(process.platform);
if (isWindows) {
expect(exitCode).toBe(1);
expect(exitSignal).toBe(null);
} else {
expect(exitCode).toBe(1);
expect(exitSignal).toBe(null);
}
});
describe('denoUnstable', async () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should not include the --unstable flag by default', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript);
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).not.toContain('--unstable');
});
it('should not include the --unstable flag by when denoUnstable is false', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript, { denoUnstable: false });
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).not.toContain('--unstable');
});
it('should include the --unstable flag when denoUnstable is true', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript, { denoUnstable: true });
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).toContain('--unstable');
});
it('should allow fine-grained unstable with an object parameter', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript, {
denoUnstable: {
temporal: true,
broadcastChannel: true,
},
});
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).toContain('--unstable-temporal');
expect(args).toContain('--unstable-broadcast-channel');
});
});
describe('unsafelyIgnoreCertificateErrors', async () => {
afterEach(() => {
jest.clearAllMocks();
});
it('supports the --unsafely-ignore-certificate-errors flag', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(
`self.onmessage = (e) => {
if (e.data.type === 'echo') {
self.postMessage('hi');
}
};`,
{
unsafelyIgnoreCertificateErrors: true,
}
);
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve(e);
};
worker.postMessage({
type: 'echo',
});
expect(await promise).toEqual({ data: 'hi' });
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).toContain(`--unsafely-ignore-certificate-errors`);
});
});
describe('location', async () => {
afterEach(() => {
jest.clearAllMocks();
});
it('supports the --location flag to specify location.href', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
const LOCATION = 'https://xxx.com/';
worker = new DenoWorker(
`self.onmessage = (e) => {
if (e.data.type === 'echo') {
self.postMessage(location.href);
}
};`,
{
location: LOCATION,
}
);
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve(e);
};
worker.postMessage({
type: 'echo',
});
expect(await promise).toEqual({ data: LOCATION });
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).toContain(`--location=${LOCATION}`);
});
});
describe('denoCachedOnly', async () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should not include the --cached-only flag by default', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript);
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).not.toContain('--cached-only');
});
it('should not include the --cached-only flag by when denoCachedOnly is false', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript, { denoCachedOnly: false });
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).not.toContain('--cached-only');
});
it('should include the --cached-only flag when denoCachedOnly is true', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript, { denoCachedOnly: true });
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).toContain('--cached-only');
});
});
describe('denoNoCheck', async () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should not include the --no-check flag by default', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript);
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).not.toContain('--no-check');
});
it('should not include the --no-check flag by when denoNoCheck is false', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript, { denoNoCheck: false });
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).not.toContain('--no-check');
});
it('should include the --no-check flag when denoNoCheck is true', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript, { denoNoCheck: true });
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).toContain('--no-check');
});
});
describe('denoImportMapPath', async () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should not include --import-map by default', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript);
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).not.toContain('--import-map');
});
it('should not set --import-map by when denoImportMapPath is empty', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript, { denoImportMapPath: '' });
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).not.toContain('--import-map');
});
it('should set --import-map when denoImportMapPath is nonempty', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
const importMapPath = path.resolve(
'./src/test/import_map.json'
);
worker = new DenoWorker(echoScript, {
denoImportMapPath: importMapPath,
});
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).toContain(`--import-map=${importMapPath}`);
});
});
describe('denoLockFilePath', async () => {
/*
* generateLockFile() shells out to deno to create a lock file from index.ts`. This lock file is created
* to be a temporary file (stored in the gitignored ./tmp) It returns the fully qualified path to the temp
* file as a string. Cleanup is to handled by the test cleanup.
*/
function generateLockFile(): Promise<string> {
const tmpDirPath = path.resolve('./tmp');
if (!existsSync(tmpDirPath)) {
mkdirSync(tmpDirPath);
}
const lockFileName = randomBytes(32).toString('hex');
const lockFilePath = path.join(tmpDirPath, lockFileName);
lockFiles.add(lockFilePath);
const denoIndexPath = path.resolve('./deno/index.ts');
const process = child_process.exec(
`deno cache --lock=${lockFilePath} --lock-write ${denoIndexPath}`
);
const promise = new Promise<string>((res) => {
process.on('exit', () => res(lockFilePath));
});
return promise;
}
let lockFiles: Set<string>;
beforeEach(() => {
lockFiles = new Set<string>();
});
afterEach(() => {
lockFiles.forEach((file) => unlinkSync(file));
jest.clearAllMocks();
});
it('should not include --lock by default', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript);
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).not.toContain('--lock');
});
it('should not set --lock by when denoLockFilePath is empty', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript, { denoLockFilePath: '' });
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).not.toContain('--lock');
});
it('should set --lock when denoLockFilePath is nonempty', async () => {
const lockFilePath = await generateLockFile();
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript, {
denoLockFilePath: lockFilePath,
});
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
worker.terminate();
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).toContain(`--lock=${lockFilePath}`);
});
});
describe('denoV8Flags', async () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should not include --v8-flags by default', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript);
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).not.toContain('--v8-flags');
});
it('should not set --v8-flags by when denoV8Flags is empty', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript, { denoV8Flags: [] });
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).not.toContain('--v8-flags');
});
it('should set --v8-flags denoV8Flags has a single flag set ', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript, {
denoV8Flags: ['--max-old-space-size=2048'],
});
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).toContain(`--v8-flags=--max-old-space-size=2048`);
});
it('should set --v8-flags when denoV8Flags has multiple flags set', async () => {
const spawnSpy = jest.spyOn(child_process, 'spawn');
worker = new DenoWorker(echoScript, {
denoV8Flags: [
'--max-old-space-size=2048',
'--max-heap-size=2048',
],
});
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
const call = spawnSpy.mock.calls[0];
const [_deno, args] = call;
expect(args).toContain(
`--v8-flags=--max-old-space-size=2048,--max-heap-size=2048`
);
});
});
describe('spawnOptions', async () => {
it('should be able to pass spawn options', async () => {
worker = new DenoWorker(envScript, {
permissions: {
allowEnv: true,
},
spawnOptions: {
env: {
...process.env,
HELLO: 'WORLD',
},
},
});
let ret: any;
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
ret = e.data;
resolve();
};
worker.postMessage({
type: 'env',
name: 'HELLO',
});
await promise;
expect(ret).toEqual('WORLD');
});
});
});
describe('data types', () => {
let ret: any;
let resolve: any;
let promise: Promise<any>;
beforeEach(() => {
worker = new DenoWorker(echoScript);
promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
ret = e.data;
resolve();
};
});
it('should be able pass BigInt values', async () => {
worker.postMessage({
type: 'echo',
num: BigInt(9007199254740991),
});
await promise;
expect(ret).toEqual({
type: 'echo',
num: BigInt(9007199254740991),
});
});
it('should be able pass Date values', async () => {
worker.postMessage({
type: 'echo',
time: new Date(2020, 7, 21, 7, 54, 32, 412),
});
await promise;
expect(ret).toEqual({
type: 'echo',
time: new Date(2020, 7, 21, 7, 54, 32, 412),
});
});
it('should be able pass RegExp values', async () => {
worker.postMessage({
type: 'echo',
regex: new RegExp('^hellosworld$'),
});
await promise;
expect(ret).toEqual({
type: 'echo',
regex: new RegExp('^hellosworld$'),
});
});
it('should be able pass Map values', async () => {
worker.postMessage({
type: 'echo',
map: new Map([
['key', 'value'],
['key2', 'value2'],
]),
});
await promise;
expect(ret).toEqual({
type: 'echo',
map: new Map([
['key', 'value'],
['key2', 'value2'],
]),
});
});
it('should be able pass Set values', async () => {
worker.postMessage({
type: 'echo',
set: new Set(['abc', 'def']),
});
await promise;
expect(ret).toEqual({
type: 'echo',
set: new Set(['abc', 'def']),
});
});
it('should be able pass Error values', async () => {
worker.postMessage({
type: 'echo',
error: new Error('my error'),
});
await promise;
expect(ret).toEqual({
type: 'echo',
error: new Error('my error'),
});
});
});
describe('transfer', () => {
beforeEach(() => {
worker = new DenoWorker(pingScript);
});
it('should be able to pass a MessagePort to the worker', async () => {
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
let channel = new MessageChannel();
channel.port1.onmessage = (e) => {
resolve(e.data);
};
worker.postMessage(
{
type: 'port',
port: channel.port2,
},
[channel.port2]
);
channel.port1.postMessage('ping');
let ret = await promise;
expect(ret).toEqual('pong');
});
it('should be able to recieve a MessagePort from the worker', async () => {
let resolve: any;
let promise1 = new Promise<any>((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
resolve(e.data);
};
worker.postMessage({
type: 'request_port',
});
let ret = await promise1;
let promise2 = new Promise<any>((res, rej) => {
resolve = res;
});
expect(ret.type).toEqual('port');
expect(ret.port).toBeInstanceOf(MessagePort);
// Ports from the worker should have String IDs
// so they don't interfere with the ones generated from the host.
expect(typeof ret.port.channelID).toBe('string');
ret.port.onmessage = (e: any) => {
resolve(e.data);
};
ret.port.postMessage('ping');
let final = await promise2;
expect(final).toEqual('pong');
});
});
describe('terminate()', () => {
it('should kill the deno process when terminated immediately', async () => {
let denoProcesses = await getDenoProcesses();
expect(denoProcesses).toEqual([]);
worker = new DenoWorker(echoScript, {
permissions: {
allowNet: [`https://google.com`],
},
});
worker.terminate();
denoProcesses = await getDenoProcesses();
expect(denoProcesses).toEqual([]);
});
it('should kill the deno process when terminated after the initial connection', async () => {
let denoProcesses = await getDenoProcesses();
expect(denoProcesses).toEqual([]);
worker = new DenoWorker(echoScript, {
permissions: {
allowNet: [`https://google.com`],
},
});
let ret: any;
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
ret = e.data;
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
worker.terminate();
denoProcesses = await getDenoProcesses();
expect(denoProcesses).toEqual([]);
});
it('should kill the deno process when terminated while sending data', async () => {
let denoProcesses = await getDenoProcesses();
expect(denoProcesses).toEqual([]);
worker = new DenoWorker(echoScript, {
permissions: {
allowNet: [`https://google.com`],
},
});
let ret: any;
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
ret = e.data;
resolve();
};
worker.terminate();
worker.postMessage({
type: 'echo',
message: 'Hello',
});
denoProcesses = await getDenoProcesses();
expect(denoProcesses).toEqual([]);
});
it('should kill the deno process when terminated while recieving data', async () => {
let denoProcesses = await getDenoProcesses();
expect(denoProcesses).toEqual([]);
worker = new DenoWorker(echoScript, {
permissions: {
allowNet: [`https://google.com`],
},
});
let ret: any;
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
ret = e.data;
console.log('Message');
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await Promise.resolve();
worker.terminate();
denoProcesses = await getDenoProcesses();
expect(denoProcesses).toEqual([]);
});
it('should kill the deno process when terminated while the script is in an infinite loop', async () => {
let denoProcesses = await getDenoProcesses();
expect(denoProcesses).toEqual([]);
worker = new DenoWorker(infiniteScript, {
permissions: {
allowNet: [`https://google.com`],
},
});
let ret: any;
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
ret = e.data;
resolve();
};
await promise;
worker.postMessage({
type: 'echo',
message: 'Hello',
});
worker.terminate();
denoProcesses = await getDenoProcesses();
expect(denoProcesses).toEqual([]);
});
it('should call onexit', async () => {
let denoProcesses = await getDenoProcesses();
expect(denoProcesses).toEqual([]);
worker = new DenoWorker(echoScript, {
permissions: {
allowNet: [`https://google.com`],
},
});
let exitCode: number;
let exitSignal: string;
worker.onexit = (code, signal) => {
exitCode = code;
exitSignal = signal;
};
let ret: any;
let resolve: any;
let promise = new Promise((res, rej) => {
resolve = res;
});
worker.onmessage = (e) => {
ret = e.data;
resolve();
};
worker.postMessage({
type: 'echo',
message: 'Hello',
});
await promise;
expect(exitCode).toBeUndefined();
expect(exitSignal).toBeUndefined();
worker.terminate();
denoProcesses = await getDenoProcesses();
expect(denoProcesses).toEqual([]);
const isWindows = /^win/.test(process.platform);
if (isWindows) {
expect(exitCode).toBe(1);
expect(exitSignal).toBe(null);
} else {
expect(exitCode).toBe(null);
expect(exitSignal).toBe('SIGKILL');
}
});
it('should gracefully handle Deno out-of-memory', async () => {
let denoProcesses = await getDenoProcesses();
expect(denoProcesses).toEqual([]);
worker = new DenoWorker(memoryCrashFile, {
denoV8Flags: ['--max-heap-size=10'],
logStdout: true,
});
worker.postMessage({});
const exitValues = await new Promise<
[number | null, string | null]
>((resolve) => {
worker.onexit = (...args) => resolve(args);
});
const isWindows = /^win/.test(process.platform);
if (isWindows) {
expect(typeof exitValues[0]).toEqual('number');
expect(exitValues[1]).toEqual(null);
} else {
expect(exitValues).toEqual([null, 'SIGTRAP']);
}
worker.terminate();
});
});
describe('closeSocket()', () => {
it('should allow natural exit if closeSocket is called after message is received', async () => {
worker = new DenoWorker(unresolvedPromiseScript);
let resolveExit: any;
let exit = new Promise((resolve) => {
resolveExit = resolve;
});
worker.onexit = () => resolveExit();
await new Promise<void>(
(resolve) =>
(worker.onmessage = (e) => {
worker.closeSocket();
resolve();
})
);
await exit;
});
it('should allow natural exit if closeSocket is called before socket is open', async () => {
worker = new DenoWorker(unresolvedPromiseScript);
let resolveExit: any;
let exit = new Promise((resolve) => {
resolveExit = resolve;
});
worker.onexit = () => resolveExit();
worker.closeSocket();
await exit;
});
});
});
async function getDenoProcesses() {
const list = await psList();
const denoProcesses = list.filter(
(p) => /^deno/.test(p.name) && p.cmd !== 'deno lsp'
);
return denoProcesses;
}