UNPKG

deno-vm

Version:

A VM module that provides a secure runtime environment via Deno.

1,346 lines (1,079 loc) 41.3 kB
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; }