diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index f98df47..f042379 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -9,19 +9,14 @@ on:
jobs:
lint:
runs-on: ubuntu-latest
- name: XO & Prettier
steps:
- name: Setup repo
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Setup node
uses: actions/setup-node@v4
with:
- # Pinned: the legacy xo@0.20 toolchain crashes on modern Node
- # (util.isDate was removed). Revisit when the toolchain is updated.
- node-version: 14
- - name: Install dev dependencies
- run: |
- npm install --only=dev
- npm list --dev --depth=0
+ node-version: 22
+ - name: Install dependencies
+ run: npm install
- name: Run lint
run: npm run lint
diff --git a/.github/workflows/test-macos.yml b/.github/workflows/test-macos.yml
index 0a8bf5b..5e20072 100644
--- a/.github/workflows/test-macos.yml
+++ b/.github/workflows/test-macos.yml
@@ -9,32 +9,20 @@ on:
jobs:
test:
runs-on: macos-latest
- name: AVA & TSD & Benchmark & Codecov
strategy:
fail-fast: false
+ # Node 26+ is not yet supported by the coverage tool (c8); revisit
+ # "current" once the toolchain catches up.
matrix:
- node: [current, 22, 20, 18]
+ node: [24, 22, 20, 18]
steps:
- name: Setup repo
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup node ${{ matrix.node }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- - name: Install lib dependencies
- run: |
- npm install --only=prod
- npm list --prod --depth=0
- - name: Install dev dependencies
- run: |
- npm install --only=dev
- npm list --dev --depth=0
+ - name: Install dependencies
+ run: npm install
- name: Run tests
- run: npm run test
- #- name: Run type checking
- # run: npm run types
- - name: Run benchmark
- run: |
- npm run bench
- - name: Upload coverage to Codecov
- uses: codecov/codecov-action@v2
+ run: npm test
diff --git a/.github/workflows/test-ubuntu.yml b/.github/workflows/test-ubuntu.yml
index 9f0cc2c..126caf1 100644
--- a/.github/workflows/test-ubuntu.yml
+++ b/.github/workflows/test-ubuntu.yml
@@ -9,32 +9,26 @@ on:
jobs:
test:
runs-on: ubuntu-latest
- name: AVA & TSD & Benchmark & Codecov
strategy:
fail-fast: false
+ # Node 26+ is not yet supported by the coverage tool (c8); revisit
+ # "current" once the toolchain catches up.
matrix:
- node: [current, 22, 20, 18]
+ node: [24, 22, 20, 18]
steps:
- name: Setup repo
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup node ${{ matrix.node }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- - name: Install lib dependencies
- run: |
- npm install --only=prod
- npm list --prod --depth=0
- - name: Install dev dependencies
- run: |
- npm install --only=dev
- npm list --dev --depth=0
+ - name: Install dependencies
+ run: npm install
- name: Run tests
- run: npm run test
- #- name: Run type checking
- # run: npm run types
+ run: npm test
+ - name: Run type checking
+ run: npm run types
- name: Run benchmark
- run: |
- npm run bench
+ run: npm run bench
- name: Upload coverage to Codecov
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v5
diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml
index 4fcb35c..ee9130f 100644
--- a/.github/workflows/test-windows.yml
+++ b/.github/workflows/test-windows.yml
@@ -11,37 +11,20 @@ jobs:
# windows-latest is Windows Server 2025, which ships without wmic, so the
# integration tests here run against the PowerShell fallback in lib/get.js.
runs-on: windows-latest
- name: AVA & TSD & Benchmark & Codecov
strategy:
fail-fast: false
+ # Node 26+ is not yet supported by the coverage tool (c8); revisit
+ # "current" once the toolchain catches up.
matrix:
- node: [current, 22, 20, 18]
+ node: [24, 22, 20, 18]
steps:
- name: Setup repo
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup node ${{ matrix.node }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- - name: Install lib dependencies
- run: |
- npm install --only=prod
- npm list --prod --depth=0
- - name: Install dev dependencies
- run: |
- npm install --only=dev
- npm list --dev --depth=0
+ - name: Install dependencies
+ run: npm install
- name: Run tests
- if: ${{ matrix.node <= 6 }}
- run: npm run test
- - name: Run tests
- if: ${{ !(matrix.node <= 6) }}
- run: npm run test:windows
- #- name: Run type checking
- # run: npm run types
- - name: Run benchmark
- run: |
- npm run bench
- - name: Upload coverage to Codecov
- if: ${{ matrix.node <= 6 }}
- uses: codecov/codecov-action@v2
+ run: npm test
diff --git a/bin/pidtree.js b/bin/pidtree.js
index 542876e..3e54d36 100755
--- a/bin/pidtree.js
+++ b/bin/pidtree.js
@@ -1,36 +1,28 @@
#!/usr/bin/env node
-'use strict';
-
-var os = require('os');
-var pidtree = require('..');
-
-// The method startsWith is not defined on string objects in node 0.10
-// eslint-disable-next-line no-extend-native
-String.prototype.startsWith = function(suffix) {
- return this.substring(0, suffix.length) === suffix;
-};
+import os from 'node:os';
+import pidtree from '../index.js';
function help() {
- var help =
+ console.log(
' Usage\n' +
- ' $ pidtree
@@ -83,39 +83,44 @@ Furthermore ps-tree is [unmaintained][gh:ps-tree-um]. Uuh, and a fancy [CLI](#cli) is also available! +## Install + +```bash +npm install pidtree +``` + +> **Requirements:** pidtree is an [ESM-only][gh:esm] package and requires +> **Node.js >= 18**. If you need CommonJS (`require`) or support for older +> Node.js versions, stay on [`pidtree@0.6`](https://www.npmjs.com/package/pidtree/v/0.6.1). + ## Usage ```js -var pidtree = require('pidtree') +import pidtree from 'pidtree' +// The named import works too: import {pidtree} from 'pidtree' -// Get childs of current process -pidtree(process.pid, function (err, pids) { - console.log(pids) - // => [] -}) +// Get children of the current process (a promise is returned) +const pids = await pidtree(process.pid) +console.log(pids) +// => [] // Include the given pid in the result array -pidtree(process.pid, {root: true}, function (err, pids) { - console.log(pids) - // => [727] -}) +console.log(await pidtree(process.pid, {root: true})) +// => [727] // Get all the processes of the System (-1 is a special value of this package) -pidtree(-1, function (err, pids) { - console.log(pids) - // => [530, 42, ..., 41241] -}) +console.log(await pidtree(-1)) +// => [530, 42, ..., 41241] -// Include PPID in the results -pidtree(1, {advanced: true}, function (err, pids) { +// Include the PPID in the results +console.log(await pidtree(1, {advanced: true})) +// => [{ppid: 1, pid: 530}, {ppid: 1, pid: 42}, ..., {ppid: 1, pid: 41241}] + +// A Node-style callback is also supported instead of a promise +pidtree(1, function (err, pids) { console.log(pids) - // => [{ppid: 1, pid: 530}, {ppid: 1, pid: 42}, ..., {ppid: 1, pid: 41241}] + // => [141, 42, ..., 15242] }) - -// If no callback is given it returns a promise instead -const pids = await pidtree(1) -console.log(pids) -// => [141, 42, ..., 15242] ``` ## Compatibility @@ -192,6 +197,7 @@ This project is licensed under the MIT License - see the [license][license] file [github:simonepri]: https://github.com/simonepri +[gh:esm]: https://nodejs.org/api/esm.html [gh:pidusage]: https://github.com/soyuka/pidusage [gh:ps-tree]: https://github.com/indexzero/ps-tree [gh:ps-tree-um]: https://github.com/indexzero/ps-tree/issues/30 diff --git a/test/backends.js b/test/backends.js new file mode 100644 index 0000000..70ce8e7 --- /dev/null +++ b/test/backends.js @@ -0,0 +1,60 @@ +import test from 'ava'; +import {ps} from '../lib/ps.js'; +import {wmic} from '../lib/wmic.js'; +import {powershell} from '../lib/powershell.js'; + +const backends = [ + {name: 'ps', fn: ps}, + {name: 'wmic', fn: wmic}, + {name: 'powershell', fn: powershell}, +]; + +// A drop-in replacement for lib/bin.js `run` that handles both the +// (cmd, args, done) and (cmd, args, options, done) signatures. +function fakeRun({err, stdout = '', code = 0} = {}) { + return (cmd, args, options, done) => { + if (typeof options === 'function') { + done = options; + } + + if (err) { + done(err); + return; + } + + done(null, stdout, code); + }; +} + +function call(backend, run) { + return new Promise((resolve, reject) => { + backend((error, list) => { + if (error) { + reject(error); + } else { + resolve(list); + } + }, run); + }); +} + +for (const {name, fn} of backends) { + test(`${name} parses the spawned output`, async (t) => { + const list = await call(fn, fakeRun({stdout: '0 100\n100 101\n'})); + t.deepEqual(list, [ + [0, 100], + [100, 101], + ]); + }); + + test(`${name} errors on a non-zero exit code`, async (t) => { + const error = await t.throwsAsync(call(fn, fakeRun({code: 1}))); + t.true(error.message.includes('exited with code 1')); + }); + + test(`${name} propagates a spawn error`, async (t) => { + const boom = new Error('spawn failed'); + const error = await t.throwsAsync(call(fn, fakeRun({err: boom}))); + t.is(error, boom); + }); +} diff --git a/test/bench.js b/test/bench.js index b1f1123..2fb1941 100644 --- a/test/bench.js +++ b/test/bench.js @@ -1,39 +1,31 @@ import test from 'ava'; - import tspan from 'time-span'; - -import pidtree from '..'; +import pidtree from '../index.js'; async function execute(pid, times) { const end = tspan(); - try { - for (let i = 0; i < times; i++) { - // eslint-disable-next-line no-await-in-loop - await pidtree(pid); - } - - const time = end(); - return Promise.resolve(time); - } catch (error) { - end(); - return Promise.reject(error); + for (let i = 0; i < times; i++) { + // eslint-disable-next-line no-await-in-loop + await pidtree(pid); } + + return end(); } -test.serial('should execute the benchmark', async t => { +test.serial('should execute the benchmark', async (t) => { let time = await execute(-1, 100); t.log( `Get childs of all the system's pids 100 times done in ${time.toFixed( - 3 - )} ms (${(1000 * 100 / time).toFixed(3)} op/s)` + 3, + )} ms (${((1000 * 100) / time).toFixed(3)} op/s)`, ); time = await execute(process.pid, 100); t.log( `Get childs of pid:${process.pid} 100 times done in ${time.toFixed( - 3 - )} ms (${(1000 * 100 / time).toFixed(3)} op/s)` + 3, + )} ms (${((1000 * 100) / time).toFixed(3)} op/s)`, ); - t.pass(); + t.true(time >= 0); }); diff --git a/test/bin.js b/test/bin.js new file mode 100644 index 0000000..719489b --- /dev/null +++ b/test/bin.js @@ -0,0 +1,14 @@ +import test from 'ava'; +import {stripStderr} from '../lib/bin.js'; + +test('returns undefined for empty stderr', (t) => { + t.is(stripStderr(''), undefined); +}); + +test('trims and passes through a real error message', (t) => { + t.is(stripStderr(' some error '), 'some error'); +}); + +test('strips the bogus screen size warning', (t) => { + t.is(stripStderr('your 131072x1 screen size is bogus. expect trouble'), ''); +}); diff --git a/test/get.js b/test/get.js index 2e69fc6..7feb582 100644 --- a/test/get.js +++ b/test/get.js @@ -1,83 +1,75 @@ import test from 'ava'; -import mockery from 'mockery'; - -import pify from 'pify'; - -test.before(() => { - mockery.enable({ - warnOnReplace: false, - warnOnUnregistered: false, - useCleanCache: true, +import {getWindows} from '../lib/get.js'; + +function call(wmicFn, powershellFn) { + return new Promise((resolve, reject) => { + getWindows( + (error, list) => { + if (error) { + reject(error); + } else { + resolve(list); + } + }, + wmicFn, + powershellFn, + ); }); -}); - -test.beforeEach(() => { - mockery.resetCache(); -}); - -test.after(() => { - mockery.disable(); -}); - -function osMock(platform) { - return { - EOL: '\n', - platform: () => platform, - type: () => 'type', - release: () => 'release', - }; } -test('should use wmic on Windows when it is available', async t => { - mockery.registerMock('os', osMock('win32')); - mockery.registerMock('./wmic', cb => cb(null, [[0, 100], [100, 101]])); - mockery.registerMock('./powershell', () => { - t.fail('powershell should not be used when wmic succeeds'); - }); - - const get = require('../lib/get'); - - const result = await pify(get)(); - t.deepEqual(result, [[0, 100], [100, 101]]); - - mockery.deregisterMock('os'); - mockery.deregisterMock('./wmic'); - mockery.deregisterMock('./powershell'); +test('uses wmic on Windows when it is available', async (t) => { + let powershellUsed = false; + const list = await call( + (callback) => + callback(null, [ + [0, 100], + [100, 101], + ]), + (callback) => { + powershellUsed = true; + callback(null, []); + }, + ); + t.false(powershellUsed, 'powershell should not be used when wmic succeeds'); + t.deepEqual(list, [ + [0, 100], + [100, 101], + ]); }); -test('should fall back to powershell when wmic is missing on Windows', async t => { - const enoent = new Error('spawn wmic ENOENT'); - enoent.code = 'ENOENT'; - - mockery.registerMock('os', osMock('win32')); - mockery.registerMock('./wmic', cb => cb(enoent)); - mockery.registerMock('./powershell', cb => cb(null, [[0, 777], [777, 778]])); - - const get = require('../lib/get'); - - const result = await pify(get)(); - t.deepEqual(result, [[0, 777], [777, 778]]); - - mockery.deregisterMock('os'); - mockery.deregisterMock('./wmic'); - mockery.deregisterMock('./powershell'); +test('falls back to powershell when wmic is missing', async (t) => { + const enoent = Object.assign(new Error('spawn wmic ENOENT'), { + code: 'ENOENT', + }); + const list = await call( + (callback) => callback(enoent), + (callback) => + callback(null, [ + [0, 777], + [777, 778], + ]), + ); + t.deepEqual(list, [ + [0, 777], + [777, 778], + ]); }); -test('should not fall back to powershell on a non ENOENT wmic error', async t => { +test('does not fall back to powershell on a non-ENOENT wmic error', async (t) => { + let powershellUsed = false; const boom = new Error('wmic exploded'); - - mockery.registerMock('os', osMock('win32')); - mockery.registerMock('./wmic', cb => cb(boom)); - mockery.registerMock('./powershell', () => { - t.fail('powershell should not be used on a generic wmic error'); - }); - - const get = require('../lib/get'); - - const err = await t.throws(pify(get)()); - t.is(err.message, 'wmic exploded'); - - mockery.deregisterMock('os'); - mockery.deregisterMock('./wmic'); - mockery.deregisterMock('./powershell'); + const error = await t.throwsAsync( + call( + (callback) => callback(boom), + (callback) => { + powershellUsed = true; + callback(null, []); + }, + ), + ); + t.false( + powershellUsed, + 'powershell should not be used on a generic wmic error', + ); + t.is(error, boom); }); diff --git a/test/helpers/exec/child.js b/test/helpers/exec/child.js index 9a5a307..50ee9e7 100644 --- a/test/helpers/exec/child.js +++ b/test/helpers/exec/child.js @@ -1,8 +1,8 @@ -'use strict'; +let started = false; -var started = false; -setInterval(function() { +// Keeps the process alive and prints its pid once, so the test knows it is up. +setInterval(() => { if (started) return; console.log(process.pid); started = true; -}, 100); // Does nothing, but prevents exit +}, 100); diff --git a/test/helpers/exec/parent.js b/test/helpers/exec/parent.js index 9e31bf1..fe5a3ff 100644 --- a/test/helpers/exec/parent.js +++ b/test/helpers/exec/parent.js @@ -1,25 +1,25 @@ -'use strict'; +import cp from 'node:child_process'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; -var path = require('path'); -var cp = require('child_process'); +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const script = path.join(dirname, 'child.js'); -var started = false; -var spawned = {}; -var script = path.join('test', 'helpers', 'exec', 'child.js'); +let started = false; +const spawned = {}; -for (var i = 0; i < 10; i++) { - var child = cp.spawn('node', [script]); - child.stdout.on( - 'data', - (child => { - spawned[child.pid] = true; - }).bind(this, child) - ); +for (let i = 0; i < 10; i++) { + const child = cp.spawn('node', [script]); + child.stdout.on('data', () => { + spawned[child.pid] = true; + }); } -setInterval(function() { +// Prints this process's pid only once all ten children are up, so the test can +// rely on exactly ten descendants being alive. +setInterval(() => { if (started) return; if (Object.keys(spawned).length !== 10) return; console.log(process.pid); started = true; -}, 100); // Does nothing, but prevents exit +}, 100); diff --git a/test/helpers/graph.js b/test/helpers/graph.js deleted file mode 100644 index 033c949..0000000 --- a/test/helpers/graph.js +++ /dev/null @@ -1,15 +0,0 @@ -async function deepForEach(root, fn) { - const queue = [root]; - while (queue.length > 0) { - const cur = queue.pop(); - // eslint-disable-next-line no-await-in-loop - await fn(cur); - if (Array.isArray(cur.children)) { - cur.children.forEach(c => queue.push(c)); - } - } -} - -module.exports = { - deepForEach, -}; diff --git a/test/helpers/mocks.js b/test/helpers/mocks.js deleted file mode 100644 index 265c236..0000000 --- a/test/helpers/mocks.js +++ /dev/null @@ -1,32 +0,0 @@ -import EventEmitter from 'events'; -import streamify from 'string-to-stream'; -import through from 'through'; - -// eslint-disable-next-line max-params -function spawn(stdout, stderr, error, code, signal) { - const ee = new EventEmitter(); - - ee.stdout = through(function(d) { - this.queue(d); - }); - ee.stderr = through(function(d) { - this.queue(d); - }); - - streamify(stderr).pipe(ee.stderr); - streamify(stdout).pipe(ee.stdout); - - if (error) { - ee.emit('error', error); - } else if (stderr) { - ee.stderr.on('end', () => ee.emit('close', code, signal)); - } else { - ee.stdout.on('end', () => ee.emit('close', code, signal)); - } - - return ee; -} - -export default { - spawn, -}; diff --git a/test/integration.js b/test/integration.js index 6950add..610f36f 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1,129 +1,100 @@ -import cp from 'child_process'; -import path from 'path'; - +import cp from 'node:child_process'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {promisify} from 'node:util'; import test from 'ava'; +import treeKill from 'tree-kill'; +import pidtree from '../index.js'; -import pify from 'pify'; -import treek from 'tree-kill'; - -import pidtree from '..'; +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const kill = promisify(treeKill); const scripts = { - parent: path.join(__dirname, 'helpers', 'exec', 'parent.js'), - child: path.join(__dirname, 'helpers', 'exec', 'child.js'), + parent: path.join(dirname, 'helpers', 'exec', 'parent.js'), + child: path.join(dirname, 'helpers', 'exec', 'child.js'), }; -test('should work with a single pid', async t => { - let result = await pidtree(-1, {advanced: true}); - t.log(result); +// Resolves once the spawned helper has printed its pid, meaning it (and all of +// its own children) are up and running. +function waitForReady(child) { + return new Promise((resolve, reject) => { + child.stdout.on('data', (data) => resolve(data.toString())); + child.stderr.on('data', (data) => reject(new Error(data.toString()))); + child.on('error', reject); + child.on('exit', () => reject(new Error('the helper exited early'))); + }); +} +test('should work with a single pid', async (t) => { + let result = await pidtree(-1, {advanced: true}); t.true(Array.isArray(result)); - result.forEach((p, i) => { - t.is(typeof p, 'object', i); - t.is(typeof p.ppid, 'number', i); - t.false(isNaN(p.ppid), i); - t.is(typeof p.pid, 'number', i); - t.false(isNaN(p.pid), i); - }); + for (const entry of result) { + t.is(typeof entry.ppid, 'number'); + t.false(Number.isNaN(entry.ppid)); + t.is(typeof entry.pid, 'number'); + t.false(Number.isNaN(entry.pid)); + } result = await pidtree(-1); - t.true(Array.isArray(result)); - result.forEach((p, i) => { - t.is(typeof p, 'number', i); - t.false(isNaN(p), i); - }); + for (const entry of result) { + t.is(typeof entry, 'number'); + t.false(Number.isNaN(entry)); + } }); -test('show work with a Parent process which has zero Child processes', async t => { +test('should work with a parent which has zero child processes', async (t) => { const child = cp.spawn('node', [scripts.child]); - - try { - await new Promise((resolve, reject) => { - child.stdout.on('data', d => resolve(d.toString())); - child.stderr.on('data', d => reject(d.toString())); - child.on('error', reject); - child.on('exit', reject); - }); - } catch (error) { - await pify(treek)(child.pid); - t.notThrows(() => { - throw error; - }); - } + await waitForReady(child); const children = await pidtree(child.pid); - await pify(treek)(child.pid); + await kill(child.pid); t.is(children.length, 0, 'There should be no active child processes'); }); -test('show work with a Parent process which has ten Child processes', async t => { +test('should work with a parent which has ten child processes', async (t) => { const parent = cp.spawn('node', [scripts.parent]); - - try { - await new Promise((resolve, reject) => { - parent.stdout.on('data', d => resolve(d.toString())); - parent.stderr.on('data', d => reject(d.toString())); - parent.on('error', reject); - parent.on('exit', reject); - }); - } catch (error) { - await pify(treek)(parent.pid); - t.notThrows(() => { - throw error; - }); - } + await waitForReady(parent); const children = await pidtree(parent.pid); - await pify(treek)(parent.pid); + await kill(parent.pid); t.is(children.length, 10, 'There should be 10 active child processes'); }); -test('show include the root if the root option is passsed', async t => { +test('should include the root when the root option is passed', async (t) => { const child = cp.spawn('node', [scripts.child]); - - try { - await new Promise((resolve, reject) => { - child.stdout.on('data', d => resolve(d.toString())); - child.stderr.on('data', d => reject(d.toString())); - child.on('error', reject); - child.on('exit', reject); - }); - } catch (error) { - await pify(treek)(child.pid); - t.notThrows(() => { - throw error; - }); - } + await waitForReady(child); const children = await pidtree(child.pid, {root: true, advanced: true}); - await pify(treek)(child.pid); + await kill(child.pid); - t.deepEqual( - children, - [{ppid: process.pid, pid: child.pid}], - 'There should be the root pid in the array' - ); + t.deepEqual(children, [{ppid: process.pid, pid: child.pid}]); }); -test('should throw an error if an invalid pid is provided', async t => { - let err = await t.throws(pidtree(null)); - t.is(err.message, 'The pid provided is invalid'); - err = await t.throws(pidtree([])); - t.is(err.message, 'The pid provided is invalid'); - err = await t.throws(pidtree('invalid')); - t.is(err.message, 'The pid provided is invalid'); - err = await t.throws(pidtree(-2)); - t.is(err.message, 'The pid provided is invalid'); +test('should throw an error if an invalid pid is provided', async (t) => { + for (const bad of [null, [], 'invalid', -2]) { + // eslint-disable-next-line no-await-in-loop + const error = await t.throwsAsync(pidtree(bad)); + t.is(error.message, 'The pid provided is invalid'); + } }); -test('should throw an error if the pid does not exists', async t => { - const err = await t.throws(pidtree(65535)); - t.is(err.message, 'No matching pid found'); +test('should throw an error if the pid does not exist', async (t) => { + const error = await t.throwsAsync(pidtree(65_535)); + t.is(error.message, 'No matching pid found'); }); -test.cb("should use the callback if it's provided", t => { - pidtree(process.pid, t.end); +test('should use the callback when one is provided', async (t) => { + const list = await new Promise((resolve, reject) => { + pidtree(process.pid, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + t.true(Array.isArray(list)); }); diff --git a/test/parse.js b/test/parse.js new file mode 100644 index 0000000..f7e912c --- /dev/null +++ b/test/parse.js @@ -0,0 +1,43 @@ +import test from 'ava'; +import {parse} from '../lib/parse.js'; + +test('parses ps style output and skips the header', (t) => { + const stdout = 'PPID PID\n 1 430\n 430 432\n 1 7166\n'; + t.deepEqual(parse(stdout), [ + [1, 430], + [430, 432], + [1, 7166], + ]); +}); + +test(String.raw`parses wmic style output with \r\r\n line endings`, (t) => { + const stdout = + 'ParentProcessId ProcessId\r\r\n' + + '0 777\r\r\n' + + '777 778\r\r\n'; + t.deepEqual(parse(stdout), [ + [0, 777], + [777, 778], + ]); +}); + +test('parses powershell style output without a header', (t) => { + const stdout = '0 777\r\n777 778\r\n0 779\r\n'; + t.deepEqual(parse(stdout), [ + [0, 777], + [777, 778], + [0, 779], + ]); +}); + +test('skips blank and non-numeric lines', (t) => { + const stdout = '\n0 777\nsome banner line\n777 778\n'; + t.deepEqual(parse(stdout), [ + [0, 777], + [777, 778], + ]); +}); + +test('returns an empty list for empty output', (t) => { + t.deepEqual(parse(''), []); +}); diff --git a/test/powershell.js b/test/powershell.js deleted file mode 100644 index 734f3b5..0000000 --- a/test/powershell.js +++ /dev/null @@ -1,66 +0,0 @@ -import test from 'ava'; -import mockery from 'mockery'; - -import pify from 'pify'; - -import mocks from './helpers/mocks'; - -test.before(() => { - mockery.enable({ - warnOnReplace: false, - warnOnUnregistered: false, - useCleanCache: true, - }); -}); - -test.beforeEach(() => { - mockery.resetCache(); -}); - -test.after(() => { - mockery.disable(); -}); - -test('should parse powershell output on Windows', async t => { - const stdout = '0 777\r\n777 778\r\n0 779\r\n'; - - mockery.registerMock('child_process', { - spawn: () => mocks.spawn(stdout, '', null, 0, null), - }); - mockery.registerMock('os', { - EOL: '\n', - platform: () => 'win32', - type: () => 'type', - release: () => 'release', - }); - - const powershell = require('../lib/powershell'); - - const result = await pify(powershell)(); - t.deepEqual(result, [[0, 777], [777, 778], [0, 779]]); - - mockery.deregisterMock('child_process'); - mockery.deregisterMock('os'); -}); - -test('should ignore non numeric lines in powershell output', async t => { - const stdout = '\r\n0 777\r\nsome banner line\r\n777 778\r\n'; - - mockery.registerMock('child_process', { - spawn: () => mocks.spawn(stdout, '', null, 0, null), - }); - mockery.registerMock('os', { - EOL: '\n', - platform: () => 'win32', - type: () => 'type', - release: () => 'release', - }); - - const powershell = require('../lib/powershell'); - - const result = await pify(powershell)(); - t.deepEqual(result, [[0, 777], [777, 778]]); - - mockery.deregisterMock('child_process'); - mockery.deregisterMock('os'); -}); diff --git a/test/ps.js b/test/ps.js deleted file mode 100644 index a1be0d2..0000000 --- a/test/ps.js +++ /dev/null @@ -1,136 +0,0 @@ -import test from 'ava'; -import mockery from 'mockery'; - -import pify from 'pify'; - -import mocks from './helpers/mocks'; - -test.before(() => { - mockery.enable({ - warnOnReplace: false, - warnOnUnregistered: false, - useCleanCache: true, - }); -}); - -test.beforeEach(() => { - mockery.resetCache(); -}); - -test.after(() => { - mockery.disable(); -}); - -test('should parse ps output on Darwin', async t => { - const stdout = - 'PPID PID\n' + - ' 1 430\n' + - ' 430 432\n' + - ' 1 727\n' + - ' 1 7166\n'; - - mockery.registerMock('child_process', { - spawn: () => mocks.spawn(stdout, '', null, 0, null), - }); - mockery.registerMock('os', { - EOL: '\n', - platform: () => 'darwin', - type: () => 'type', - release: () => 'release', - }); - - const ps = require('../lib/ps'); - - const result = await pify(ps)(); - t.deepEqual(result, [[1, 430], [430, 432], [1, 727], [1, 7166]]); - - mockery.deregisterMock('child_process'); - mockery.deregisterMock('os'); -}); - -test('should parse ps output on *nix', async t => { - const stdout = - 'PPID PID\n' + - ' 1 430\n' + - ' 430 432\n' + - ' 1 727\n' + - ' 1 7166\n'; - - mockery.registerMock('child_process', { - spawn: () => mocks.spawn(stdout, '', null, 0, null), - }); - mockery.registerMock('os', { - EOL: '\n', - platform: () => 'linux', - type: () => 'type', - release: () => 'release', - }); - - const ps = require('../lib/ps'); - - const result = await pify(ps)(); - t.deepEqual(result, [[1, 430], [430, 432], [1, 727], [1, 7166]]); - - mockery.deregisterMock('child_process'); - mockery.deregisterMock('os'); -}); - -test('should throw if stderr contains an error', async t => { - const stdout = - 'PPID PID\n' + - ' 1 430\n' + - ' 430 432\n' + - ' 1 727\n' + - ' 1 7166\n'; - - mockery.registerMock('child_process', { - spawn: () => mocks.spawn(stdout, 'Some error', null, 0, null), - }); - mockery.registerMock('os', { - EOL: '\n', - platform: () => 'linux', - type: () => 'type', - release: () => 'release', - }); - - const ps = require('../lib/ps'); - - await t.throws(pify(ps)()); - - mockery.deregisterMock('child_process'); - mockery.deregisterMock('os'); -}); - -test('should not throw if stderr contains the "bogus screen" error message', async t => { - const stdout = - 'PPID PID\n' + - ' 1 430\n' + - ' 430 432\n' + - ' 1 727\n' + - ' 1 7166\n'; - - mockery.registerMock('child_process', { - spawn: () => - mocks.spawn( - stdout, - 'your 131072x1 screen size is bogus. expect trouble', - null, - 0, - null - ), - }); - mockery.registerMock('os', { - EOL: '\n', - platform: () => 'linux', - type: () => 'type', - release: () => 'release', - }); - - const ps = require('../lib/ps'); - - const result = await pify(ps)(); - t.deepEqual(result, [[1, 430], [430, 432], [1, 727], [1, 7166]]); - - mockery.deregisterMock('child_process'); - mockery.deregisterMock('os'); -}); diff --git a/test/wmic.js b/test/wmic.js deleted file mode 100644 index 506cedb..0000000 --- a/test/wmic.js +++ /dev/null @@ -1,48 +0,0 @@ -import test from 'ava'; -import mockery from 'mockery'; - -import pify from 'pify'; - -import mocks from './helpers/mocks'; - -test.before(() => { - mockery.enable({ - warnOnReplace: false, - warnOnUnregistered: false, - useCleanCache: true, - }); -}); - -test.beforeEach(() => { - mockery.resetCache(); -}); - -test.after(() => { - mockery.disable(); -}); - -test('should parse wmic output on Windows', async t => { - const stdout = - `ParentProcessId ProcessId\r\r\n` + - `0 777 \r\r\n` + - `777 778 \r\r\n` + - `0 779 \r\r\n\r\r\n`; - - mockery.registerMock('child_process', { - spawn: () => mocks.spawn(stdout, '', null, 0, null), - }); - mockery.registerMock('os', { - EOL: '\r\n', - platform: () => 'linux', - type: () => 'type', - release: () => 'release', - }); - - const wmic = require('../lib/wmic'); - - const result = await pify(wmic)(); - t.deepEqual(result, [[0, 777], [777, 778], [0, 779]]); - - mockery.deregisterMock('child_process'); - mockery.deregisterMock('os'); -});