diff --git a/packages/stryker/src/child-proxy/ChildProcessProxyWorker.ts b/packages/stryker/src/child-proxy/ChildProcessProxyWorker.ts index 13dce8b733..7deb5edb1f 100644 --- a/packages/stryker/src/child-proxy/ChildProcessProxyWorker.ts +++ b/packages/stryker/src/child-proxy/ChildProcessProxyWorker.ts @@ -1,9 +1,12 @@ import { serialize, deserialize } from '../utils/objectUtils'; import { WorkerMessage, WorkerMessageKind, ParentMessage, autoStart } from './messageProtocol'; -import { setGlobalLogLevel } from 'log4js'; +import { setGlobalLogLevel, getLogger } from 'log4js'; import PluginLoader from '../PluginLoader'; export default class ChildProcessProxyWorker { + + private log = getLogger(ChildProcessProxyWorker.name); + realSubject: any; constructor() { @@ -17,7 +20,8 @@ export default class ChildProcessProxyWorker { } private listenToParent() { - process.on('message', (serializedMessage: string) => { + + const handler = (serializedMessage: string) => { const message = deserialize(serializedMessage) as WorkerMessage; switch (message.kind) { case WorkerMessageKind.Init: @@ -26,6 +30,7 @@ export default class ChildProcessProxyWorker { const RealSubjectClass = require(message.requirePath).default; this.realSubject = new RealSubjectClass(...message.constructorArgs); this.send('init_done'); + this.removeAnyAdditionalMessageListeners(handler); break; case WorkerMessageKind.Work: const result = this.realSubject[message.methodName](...message.args); @@ -35,6 +40,24 @@ export default class ChildProcessProxyWorker { result }); }); + this.removeAnyAdditionalMessageListeners(handler); + break; + } + }; + process.on('message', handler); + } + + /** + * 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 + */ + private removeAnyAdditionalMessageListeners(exceptListener: NodeJS.MessageListener) { + process.listeners('message').forEach(listener => { + if (listener !== exceptListener) { + this.log.debug('Removing an additional message listener, we don\'t want eavesdropping on our inter-process communication: %s', listener.toString()); + process.removeListener('message', listener); } }); } diff --git a/packages/stryker/test/unit/child-proxy/ChildProcessWorkerSpec.ts b/packages/stryker/test/unit/child-proxy/ChildProcessWorkerSpec.ts index 03ab99be19..e64fd7a967 100644 --- a/packages/stryker/test/unit/child-proxy/ChildProcessWorkerSpec.ts +++ b/packages/stryker/test/unit/child-proxy/ChildProcessWorkerSpec.ts @@ -11,12 +11,19 @@ describe('ChildProcessProxyWorker', () => { let processOnStub: sinon.SinonStub; let processSendStub: sinon.SinonStub; + let processListenersStub: sinon.SinonStub; let setGlobalLogLevelStub: sinon.SinonStub; + let processRemoveListenerStub: sinon.SinonStub; let pluginLoaderMock: Mock; - let originalProcessSend: undefined | ((message: any, sendHandle?: any) => void); + let originalProcessSend: undefined | NodeJS.MessageListener; + let processes: NodeJS.MessageListener[]; beforeEach(() => { + processes = []; processOnStub = sandbox.stub(process, 'on'); + processListenersStub = sandbox.stub(process, 'listeners'); + processListenersStub.returns(processes); + processRemoveListenerStub = sandbox.stub(process, 'removeListener'); processSendStub = sandbox.stub(); // process.send is normally undefined originalProcessSend = process.send; @@ -38,36 +45,53 @@ describe('ChildProcessProxyWorker', () => { describe('after init message', () => { let sut: ChildProcessProxyWorker; + let initMessage: WorkerMessage; beforeEach(() => { sut = new ChildProcessProxyWorker(); - const initMessage: WorkerMessage = { + initMessage = { kind: WorkerMessageKind.Init, logLevel: 'FooLevel', constructorArgs: ['FooBarName'], plugins: ['fooPlugin', 'barPlugin'], requirePath: require.resolve('./HelloClass') }; - processOnStub.callArgWith(1, [serialize(initMessage)]); }); - + it('should create the correct real instance', () => { + processOnStub.callArgWith(1, [serialize(initMessage)]); expect(sut.realSubject).instanceOf(HelloClass); const actual = sut.realSubject as HelloClass; expect(actual.name).eq('FooBarName'); }); - + it('should send "init_done"', async () => { + processOnStub.callArgWith(1, [serialize(initMessage)]); const expectedWorkerResponse: ParentMessage = 'init_done'; await tick(); // make sure promise is resolved expect(processSendStub).calledWith(serialize(expectedWorkerResponse)); }); + + it('should remove any additional listeners', async () => { + // Arrange + function noop() { } + processes.push(noop); + + // Act + processOnStub.callArgWith(1, [serialize(initMessage)]); + await tick(); // make sure promise is resolved + // Assert + expect(processRemoveListenerStub).calledWith('message', noop); + }); + it('should set global log level', () => { + processOnStub.callArgWith(1, [serialize(initMessage)]); expect(setGlobalLogLevelStub).calledWith('FooLevel'); }); - + it('should load plugins', () => { + processOnStub.callArgWith(1, [serialize(initMessage)]); expect(pluginLoader.default).calledWithNew; expect(pluginLoader.default).calledWith(['fooPlugin', 'barPlugin']); expect(pluginLoaderMock.load).called; @@ -77,6 +101,7 @@ describe('ChildProcessProxyWorker', () => { async function actAndAssert(workerMessage: WorkMessage, expectedResult: WorkResult) { // Act + processOnStub.callArgWith(1, [serialize(initMessage)]); processOnStub.callArgWith(1, serialize(workerMessage)); await tick(); // Assert