diff --git a/lib/cli.mjs b/lib/cli.mjs index 01e83ab..98cd88f 100644 --- a/lib/cli.mjs +++ b/lib/cli.mjs @@ -3,6 +3,7 @@ import fs from 'fs' import fsPromise from 'fs/promises' import cluster from 'cluster' import child_process from 'child_process' +import kill from './kill.mjs' import Watcher, { EVENT_REMOVE, EVENT_UPDATE } from './watch/index.mjs' export const MESSAGE_FILES_REQUEST = 'message_files_request' @@ -41,6 +42,7 @@ export function CLI(e, overrides = {}) { this.logger = overrides.logger || console this.child_process = overrides.child_process || child_process this.process = overrides.process || process + this.kill = overrides.kill || kill this.importer = overrides.importer this.loadDefaults() } @@ -287,7 +289,7 @@ CLI.prototype.runProgram = function() { if (runningTest) { return } else { - this.worker.kill() + this.kill(this.worker.pid) } } diff --git a/lib/kill.mjs b/lib/kill.mjs new file mode 100644 index 0000000..e976ffd --- /dev/null +++ b/lib/kill.mjs @@ -0,0 +1,59 @@ +import { promisify } from 'util' +import { spawn, exec } from 'child_process' + +const execPromise = promisify(exec) + +export default function kill(pid, signal) { + let pids = new Set() + let getSpawn = null + + switch (process.platform) { + case 'win32': + return execPromise('taskkill /pid ' + pid + ' /T /F') + case 'darwin': + getSpawn = function(parentPid) { + return spawn('pgrep', ['-P', parentPid]) + } + break + default: + getSpawn = function (parentPid) { + return spawn('ps', ['-o', 'pid', '--no-headers', '--ppid', parentPid]) + } + break + } + + return buildTree(pids, getSpawn, pid) + .then(function() { + for (let pid of pids) { + try { + process.kill(pid, signal) + } catch (err) { + if (err.code !== 'ESRCH') throw err; + } + } + }) +} + +function buildTree(allPids, spawnGetChildren, parentPid) { + allPids.set(parentPid) + + let ps = spawnGetChildren(parentPid) + let data = '' + ps.stdout.on('data', function(buf) { + data += buf.toString('ascii') + }) + + return new Promise(function(res) { + ps.on('close', function(code) { + // Check if we got error code (usually means empty results) + if (code !== 0) return res() + + res(Promise.all( + allData.match(/\d+/g) + .map(Number) + .filter(pid => !bla.has(pid)) + .map(buildTree.bind(this, allPids, spawnGetChildren)) + )) + }) + }) +} diff --git a/package.json b/package.json index 9ac95fd..6f3985a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eltro", - "version": "1.4.2", + "version": "1.4.3", "description": "Eltro is a tiny no-dependancy test framework for node", "main": "index.mjs", "scripts": { diff --git a/test/cli.test.mjs b/test/cli.test.mjs index ba7d403..8049567 100644 --- a/test/cli.test.mjs +++ b/test/cli.test.mjs @@ -26,8 +26,9 @@ t.describe('CLI', function() { const cluster = { c: 3 } const process = { d: 4 } const importer = { e: 5 } + const kill = { f: 6 } - let cliTest = createSafeCli(eltro, { logger, cluster, process, importer }) + let cliTest = createSafeCli(eltro, { logger, cluster, process, importer, kill }) assert.strictEqual(cliTest.reporter, 'list') assert.strictEqual(cliTest.ignoreOnly, false) assert.strictEqual(cliTest.timeout, 2000) @@ -43,6 +44,7 @@ t.describe('CLI', function() { assert.strictEqual(cliTest.cluster, cluster) assert.strictEqual(cliTest.process, process) assert.strictEqual(cliTest.importer, importer) + assert.strictEqual(cliTest.kill, kill) }) t.test('should detect isSlave from cluster', function() { @@ -913,14 +915,16 @@ t.describe('CLI', function() { let testCluster let testChildProcess let testWorker + let testKill let cli t.beforeEach(function() { + testKill = stub() testProcess = { stdout: { write: stub() }, stderr: { write: stub() } } - testWorker = { on: stub(), once: stub(), kill: stub(), send: stub(), stderr: { on: stub() }, stdout: { on: stub() } } + testWorker = { on: stub(), once: stub(), send: stub(), stderr: { on: stub() }, stdout: { on: stub() } } testCluster = { fork: stub().returns(testWorker) } testChildProcess = { spawn: stub().returns(testWorker) } - cli = createSafeCli(null, { cluster: testCluster, child_process: testChildProcess, process: testProcess }) + cli = createSafeCli(null, { cluster: testCluster, child_process: testChildProcess, process: testProcess, kill: testKill }) }) t.describe('in test mode', function() { @@ -984,14 +988,14 @@ t.describe('CLI', function() { t.test('multiple calls should cancel', function() { cli.runProgram() - assert.notOk(testWorker.kill.called) + assert.notOk(testKill.called) assert.ok(testCluster.fork.called) testCluster.fork.reset() cli.runProgram() assert.notOk(testCluster.fork.called) - assert.notOk(testWorker.kill.called) + assert.notOk(testKill.called) }) }) @@ -1063,16 +1067,19 @@ t.describe('CLI', function() { }) t.test('multiple calls should kill', function() { + const assertPid = 12345235 + testWorker.pid = assertPid cli.runProgram() - assert.notOk(testWorker.kill.called) + assert.notOk(testKill.called) assert.ok(testChildProcess.spawn.called) testChildProcess.spawn.reset().returns(testWorker) cli.runProgram() assert.ok(testChildProcess.spawn.called) - assert.ok(testWorker.kill.called) + assert.ok(testKill.called) + assert.strictEqual(testKill.firstCall[0], assertPid) }) }) })