UNPKG

pic-js-mops

Version:

An Internet Computer Protocol canister testing library for TypeScript and JavaScript.

125 lines 4.18 kB
import { spawn } from 'node:child_process'; import { resolve } from 'node:path'; import { chmodSync, rmSync } from 'node:fs'; import { BinNotFoundError, BinStartError, BinStartMacOSArmError, BinTimeoutError, } from './error.js'; import { exists, readFileAsString, tmpFile, isArm, isDarwin, poll, } from './util/index.js'; import { Writable } from 'node:stream'; /** * This class represents the main PocketIC server. * It is responsible for maintaining the lifecycle of the server process. * See {@link PocketIc} for details on the client to use with this server. * * @category API * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * import { _SERVICE, idlFactory } from '../declarations'; * * const wasmPath = resolve('..', '..', 'canister.wasm'); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const fixture = await pic.setupCanister<_SERVICE>({ idlFactory, wasmPath }); * const { actor } = fixture; * * // perform tests... * * await pic.tearDown(); * await picServer.stop(); * ``` */ export class PocketIcServer { serverProcess; url; constructor(serverProcess, portNumber) { this.serverProcess = serverProcess; this.url = `http://127.0.0.1:${portNumber}`; } /** * Start a new PocketIC server. * * @param options Options for starting the server. * @returns An instance of the PocketIC server. */ static async start(options = {}) { const binPath = options.binPath || this.getBinPath(); await this.assertBinExists(binPath); const pid = process.ppid; const picFilePrefix = `pocket_ic_${pid}`; const portFilePath = tmpFile(`${picFilePrefix}.port`); const serverProcess = spawn(binPath, ['--port-file', portFilePath, '--ttl', options.ttl ? options.ttl.toString() : '60']); if (options.showRuntimeLogs) { serverProcess.stdout.pipe(process.stdout); } else { serverProcess.stdout.pipe(new NullStream()); } if (options.showCanisterLogs) { serverProcess.stderr.pipe(process.stderr); } else { serverProcess.stderr.pipe(new NullStream()); } serverProcess.on('error', error => { if (isArm() && isDarwin()) { throw new BinStartMacOSArmError(error); } throw new BinStartError(error); }); return await poll(async () => { const portString = await readFileAsString(portFilePath); const port = parseInt(portString); if (isNaN(port)) { throw new BinTimeoutError(); } return new PocketIcServer(serverProcess, port); }, { intervalMs: POLL_INTERVAL_MS, timeoutMs: POLL_TIMEOUT_MS }); } /** * Get the URL of the server. * * @returns The URL of the server. */ getUrl() { return this.url; } /** * Stop the server. * * @returns A promise that resolves when the server has stopped. */ async stop() { return new Promise((resolve, reject) => { this.serverProcess.on('exit', () => { resolve(); }); this.serverProcess.on('error', error => { reject(error); }); this.serverProcess.kill(); const picFilePrefix = `pocket_ic_${process.ppid}`; rmSync(tmpFile(`${picFilePrefix}.port`), { force: true }); rmSync(tmpFile(`${picFilePrefix}.ready`), { force: true }); }); } static getBinPath() { return resolve(__dirname, '..', 'pocket-ic'); } static async assertBinExists(binPath) { const binExists = await exists(binPath); if (!binExists) { throw new BinNotFoundError(binPath); } chmodSync(binPath, 0o700); } } const POLL_INTERVAL_MS = 20; const POLL_TIMEOUT_MS = 30_000; class NullStream extends Writable { _write(_chunk, _encoding, callback) { callback(); } } //# sourceMappingURL=pocket-ic-server.js.map