TheBrainFamily/chimpy

View on GitHub
src/__tests__/process-helper-spec.js

Summary

Maintainability
D
1 day
Test Coverage
// jest.dontMock('../lib/process-helper');


describe('process-helper', () => {
  describe('start', () => {
    it('spawns a child, calls the callback and returns the child', () => {
      const processHelper = require('../lib/process-helper.js');

      const child = {};
      const mockedSpawn = jest.spyOn(processHelper, "spawn");
      mockedSpawn.mockReturnValue(child);

      const options = {};
      const callback = jest.fn();
      const ret = processHelper.start(options, callback);

      expect(ret).toBe(child);
      expect(callback.mock.calls.length).toBe(1);
      expect(callback.mock.calls[0][0]).toBeFalsy();
      mockedSpawn.mockRestore();
    });

    it('waits for message if waitForMessage is provided and delegates the callback as is', () => {
      const processHelper = require('../lib/process-helper.js');
      const cp = require('child_process');

      const child = {};
      cp.spawn = jest.fn().mockReturnValue(child)
      const mockedSpawn = jest.spyOn(processHelper, "spawn");
      mockedSpawn.mockImplementation(() => {});

      const mockedWaitForMessage = jest.spyOn(processHelper, "waitForMessage");
      mockedWaitForMessage.mockImplementation(function (options, child, callback) {
        callback.apply(this, [1, 2, 3, 4]);
      });

      const options = {waitForMessage: 'not null'};
      const callback = jest.fn();
      const ret = processHelper.start(options, callback);

      expect(processHelper.waitForMessage.mock.calls.length).toBe(1);
      expect(callback.mock.calls.length).toBe(1);
      expect(callback.mock.calls[0]).toEqual([1, 2, 3, 4]);
      mockedSpawn.mockRestore();
      mockedWaitForMessage.mockRestore();
    });
  });

  describe('spawn', () => {
    it('calls spawn with the binary and args and returns the child process', () => {
      const cp = require('child_process');
      const processHelper = require('../lib/process-helper.js');

      processHelper.logOutputs = jest.fn();

      const child = {};
      cp.spawn = jest.fn().mockReturnValue(child)

      const options = {
        bin: '/someBinary',
        args: ['bunch', 'of', 'args'],
      };
      const ret = processHelper.spawn(options);

      expect(cp.spawn.mock.calls.length).toBe(1);
      expect(cp.spawn.mock.calls[0][0]).toBe(options.bin);
      expect(cp.spawn.mock.calls[0][1]).toBe(options.args);
      expect(ret).toBe(child);
    });

    it('logs the outputs of the child process', () => {
      let cp = require('child_process'),
        processHelper = require('../lib/process-helper.js');

      processHelper.logOutputs = jest.fn();

      const child = {};
      spyOn(cp, 'spawn').and.returnValue(child);

      const options = {
        prefix: 'hey bear',
      };
      const ret = processHelper.spawn(options);

      expect(processHelper.logOutputs.mock.calls.length).toBe(1);
      expect(processHelper.logOutputs.mock.calls[0][0]).toBe(options.prefix);
      expect(processHelper.logOutputs.mock.calls[0][1]).toBe(child);
    });
  });

  describe('logOutputs', () => {
    it('logs the output of the child process stderr events', () => {
      let log = require('../lib/log.js'),
        processHelper = require('../lib/process-helper.js');

      log.debug = jest.fn();
      const child = {
        stdout: {
          on: jest.fn().mockImplementation((event, eventTrigger) => {
            eventTrigger('blah');
            expect(event).toBe('data');
            expect(log.debug.mock.calls.length).toBe(1);
            expect(log.debug.mock.calls[0][0]).toBe('[chimp][prefix.stdout]');
            expect(log.debug.mock.calls[0][1]).toBe('blah');
          }),
        },
        stderr: {
          on: jest.fn().mockImplementation((event, eventTrigger) => {
            eventTrigger('blah blah');
            expect(event).toBe('data');
            expect(log.debug.mock.calls.length).toBe(2);
            expect(log.debug.mock.calls[1][0]).toBe('[chimp][prefix.stderr]');
            expect(log.debug.mock.calls[1][1]).toBe('blah blah');
          }),
        },
      };

      processHelper.logOutputs('prefix', child);
    });
  });

  describe('waitForMessage', () => {
    it('removes the listener if the success message is seen and calls the callback', () => {
      const processHelper = require('../lib/process-helper.js');

      const callback = jest.fn();

      const options = {
        prefix: '[apollo]',
        waitForMessage: 'we have lift off',
      };

      let eventToBeRemovedStdOut = false;
      let eventToBeRemovedStdErr = false;
      const child = {
        stdout: {
          on: jest.fn().mockImplementation((event, eventTrigger) => {
            eventToBeRemovedStdOut = eventTrigger;
            eventTrigger('Huston, we have lift off!');
          }),
          removeListener: jest.fn(),
        },
        stderr: {
          on: jest.fn().mockImplementation((event, eventTrigger) => {
            eventToBeRemovedStdErr = eventTrigger;
            eventTrigger('Huston, we have lift off!');
          }),
          removeListener: jest.fn(),
        },
      };

      processHelper.waitForMessage(options, child, callback);

      expect(child.stdout.removeListener.mock.calls.length).toBe(1);
      expect(child.stdout.removeListener.mock.calls[0][0]).toBe('data');
      expect(child.stdout.removeListener.mock.calls[0][1]).toBe(eventToBeRemovedStdOut);

      expect(child.stderr.removeListener.mock.calls.length).toBe(1);
      expect(child.stderr.removeListener.mock.calls[0][0]).toBe('data');
      expect(child.stderr.removeListener.mock.calls[0][1]).toBe(eventToBeRemovedStdErr);

      expect(callback.mock.calls.length).toBe(2);
      expect(callback.mock.calls[0][0]).toBeFalsy();
      expect(callback.mock.calls[1][0]).toBeFalsy();
    });

    it('calls back with an error if the error message is seen', () => {
      const processHelper = require('../lib/process-helper.js');

      const callback = jest.fn();

      const options = {
        prefix: '[apollo]',
        waitForMessage: 'not empty',
        errorMessage: 'engine failure',
      };

      const eventToBeRemoved = false;
      const child = {
        stdout: {
          on: jest.fn().mockImplementation((event, eventTrigger) => {
            eventTrigger('Huston, we have a problem - engine failure!');
          }),
        },
        stderr: {
          on: jest.fn().mockImplementation((event, eventTrigger) => {
            eventTrigger('Huston, we have a problem - engine failure!');
          }),
        },
      };

      processHelper.waitForMessage(options, child, callback);

      expect(callback.mock.calls.length).toBe(2);
      expect(callback.mock.calls[0][0]).toBe('Huston, we have a problem - engine failure!');
      expect(callback.mock.calls[1][0]).toBe('Huston, we have a problem - engine failure!');
    });
  });

  describe('kill', () => {
    it('kills the provided process, sets it to null and calls the callback when the process is dead', () => {
      const processHelper = require('../lib/process-helper.js');

      process.kill = jest.fn().mockImplementation(() => {
        // the first call checks if the process exists
        // the second call is the actual kill
        // subsequent calls are checking if the process exists
        // it takes 3 calls to go through all the execution paths for this SUT
        if (process.kill.mock.calls.length === 4) {
          throw ({code: 'ESRCH'});
        }
      });

      const options = {
        child: {
          pid: 1234,
        },
      };
      const callback = jest.fn();
      jest.useFakeTimers();
      processHelper.kill(options, callback);
      jest.runAllTimers();

      expect(process.kill.mock.calls.length).toBe(4);
      expect(process.kill.mock.calls[0][0]).toEqual(1234);
      expect(process.kill.mock.calls[0][1]).toBe(0);
      expect(process.kill.mock.calls[1][1]).toBe('SIGTERM');
      expect(process.kill.mock.calls[2][0]).toEqual(1234);
      expect(process.kill.mock.calls[2][1]).toEqual(0);
      expect(process.kill.mock.calls[3][0]).toEqual(1234);
      expect(process.kill.mock.calls[3][1]).toEqual(0);

      expect(options.child).toBe(null);

      expect(callback.mock.calls.length).toBe(1);
      expect(callback.mock.calls[0][0]).toBeFalsy();
    });
  });
});