UNPKG

@stryker-mutator/core

Version:

The extendable JavaScript mutation testing framework

146 lines 7.01 kB
import path from 'path'; import { fileURLToPath } from 'url'; import { errorToString } from '@stryker-mutator/util'; import log4js from 'log4js'; import { createInjector } from 'typed-inject'; import { commonTokens } from '@stryker-mutator/api/plugin'; import { LogConfigurator } from '../logging/index.js'; import { deserialize, serialize } from '../utils/string-utils.js'; import { coreTokens, provideLogger, PluginCreator } from '../di/index.js'; import { PluginLoader } from '../di/plugin-loader.js'; import { ParentMessageKind, WorkerMessageKind } from './message-protocol.js'; export class ChildProcessProxyWorker { constructor(injectorFactory) { this.injectorFactory = injectorFactory; // Make sure to bind the methods in order to ensure the `this` pointer this.handleMessage = this.handleMessage.bind(this); // Start listening before sending the spawned message process.on('message', this.handleMessage); this.send({ kind: ParentMessageKind.Ready }); } send(value) { if (process.send) { const str = serialize(value); process.send(str); } } handleMessage(serializedMessage) { const message = deserialize(String(serializedMessage)); switch (message.kind) { case WorkerMessageKind.Init: // eslint-disable-next-line @typescript-eslint/no-floating-promises -- No handle needed, handleInit has try catch this.handleInit(message); this.removeAnyAdditionalMessageListeners(this.handleMessage); break; case WorkerMessageKind.Call: // eslint-disable-next-line @typescript-eslint/no-floating-promises -- No handle needed, handleCall has try catch this.handleCall(message); this.removeAnyAdditionalMessageListeners(this.handleMessage); break; case WorkerMessageKind.Dispose: const sendCompleted = () => { this.send({ kind: ParentMessageKind.DisposeCompleted }); }; LogConfigurator.shutdown().then(sendCompleted).catch(sendCompleted); break; } } async handleInit(message) { try { LogConfigurator.configureChildProcess(message.loggingContext); this.log = log4js.getLogger(ChildProcessProxyWorker.name); this.handlePromiseRejections(); // Load plugins in the child process const pluginInjector = provideLogger(this.injectorFactory()) .provideValue(commonTokens.options, message.options) .provideValue(commonTokens.fileDescriptions, message.fileDescriptions); const pluginLoader = pluginInjector.injectClass(PluginLoader); const { pluginsByKind } = await pluginLoader.load(message.pluginModulePaths); const injector = pluginInjector .provideValue(coreTokens.pluginsByKind, pluginsByKind) .provideClass(coreTokens.pluginCreator, PluginCreator); const childModule = await import(message.modulePath); const RealSubjectClass = childModule[message.namedExport]; const workingDir = path.resolve(message.workingDirectory); if (process.cwd() !== workingDir) { this.log.debug(`Changing current working directory for this process to ${workingDir}`); process.chdir(workingDir); } // eslint-disable-next-line @typescript-eslint/no-unsafe-argument this.realSubject = injector.injectClass(RealSubjectClass); this.send({ kind: ParentMessageKind.Initialized }); } catch (err) { this.send({ error: errorToString(err), kind: ParentMessageKind.InitError, }); } } async handleCall(message) { try { const result = await this.doCall(message); this.send({ correlationId: message.correlationId, kind: ParentMessageKind.CallResult, result, }); } catch (err) { this.send({ correlationId: message.correlationId, error: errorToString(err), kind: ParentMessageKind.CallRejection, }); } } doCall(message) { if (typeof this.realSubject[message.methodName] === 'function') { return this.realSubject[message.methodName](...message.args); } else { return this.realSubject[message.methodName]; } } /** * Remove any addition message listeners that might me eavesdropping. * the @ngtools/webpack plugin listens to messages and throws an error whenever it could not handle a message * @see https://github.com/angular/angular-cli/blob/f776d3cf7982b64734c57fe4407434e9f4ec09f7/packages/%40ngtools/webpack/src/type_checker.ts#L79 * @param exceptListener The listener that should remain */ removeAnyAdditionalMessageListeners(exceptListener) { process.listeners('message').forEach((listener) => { var _a; if (listener !== exceptListener) { (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug("Removing an additional message listener, we don't want eavesdropping on our inter-process communication: %s", listener.toString()); process.removeListener('message', listener); } }); } /** * During mutation testing, it's to be expected that promise rejections are not handled synchronously anymore (or not at all) * Let's handle those events so future versions of node don't crash * See issue 350: https://github.com/stryker-mutator/stryker-js/issues/350 */ handlePromiseRejections() { const unhandledRejections = []; process.on('unhandledRejection', (reason, promise) => { var _a; const unhandledPromiseId = unhandledRejections.push(promise); (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug(`UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: ${unhandledPromiseId}): ${reason}`); }); process.on('rejectionHandled', (promise) => { var _a; const unhandledPromiseId = unhandledRejections.indexOf(promise) + 1; (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug(`PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: ${unhandledPromiseId})`); }); } } // Prevent side effects for merely importing the file // Only actually start the child worker when it is requested // Stryker disable all if (fileURLToPath(import.meta.url) === process.argv[1]) { new ChildProcessProxyWorker(createInjector); } // Stryker restore all //# sourceMappingURL=child-process-proxy-worker.js.map