From a4b557042eaf927b65e89d3d99d49092533c4827 Mon Sep 17 00:00:00 2001 From: Andrey Chalkin Date: Thu, 26 Sep 2024 19:24:19 +0200 Subject: [PATCH] feat: implement isAlive check - Implemented isAlive check for start and status commands - Added separated option for commands - Created helper function to obtain socket --- bin/eslint_d.js | 16 ++++-- lib/commands.js | 3 + lib/forwarder.js | 76 ++++++++++++++++++++---- lib/forwarder.test.js | 131 +++++++++++++++++++++++++++++++----------- lib/launcher.js | 10 +--- lib/launcher.test.js | 3 +- lib/service.js | 28 +++++++-- lib/service.test.js | 32 ++++++----- lib/status.js | 8 ++- lib/status.test.js | 46 ++++++++++++--- 10 files changed, 267 insertions(+), 86 deletions(-) create mode 100644 lib/commands.js diff --git a/bin/eslint_d.js b/bin/eslint_d.js index 7355fd9..a000910 100755 --- a/bin/eslint_d.js +++ b/bin/eslint_d.js @@ -1,8 +1,8 @@ #!/usr/bin/env node -import { loadConfig } from '../lib/config.js'; +import { loadConfig, removeConfig } from '../lib/config.js'; import { createResolver } from '../lib/resolver.js'; -import { forwardToDaemon } from '../lib/forwarder.js'; +import { forwardToDaemon, isAlive } from '../lib/forwarder.js'; import { launchDaemon, stopDaemon } from '../lib/launcher.js'; import { filesHash } from '../lib/hash.js'; @@ -31,11 +31,17 @@ const command = process.argv[2]; ]); switch (command) { case 'start': - if (config) { + if (await isAlive(config)) { console.log('eslint_d: Already running'); - } else { - await launchDaemon(resolver, hash); + return; + } + + if (config) { + await removeConfig(resolver); } + + await launchDaemon(resolver, hash); + return; case 'stop': if (config) { diff --git a/lib/commands.js b/lib/commands.js new file mode 100644 index 0000000..da2b6b3 --- /dev/null +++ b/lib/commands.js @@ -0,0 +1,3 @@ +export const PING_COMMAND = 'ESLINT_D_PING'; +export const SHUTDOWN_COMMAND = 'ESLINT_D_SHUTDOWN'; +export const LINT_COMMAND = 'ESLINT_D_LINT'; diff --git a/lib/forwarder.js b/lib/forwarder.js index 9a0b60d..8ec395e 100644 --- a/lib/forwarder.js +++ b/lib/forwarder.js @@ -1,15 +1,45 @@ import net from 'node:net'; import supportsColor from 'supports-color'; import { removeConfig } from '../lib/config.js'; +import { LINT_COMMAND, PING_COMMAND } from './commands.js'; /** - * @import { Config} from '../lib/config.js' - * @import { Resolver} from '../lib/resolver.js' + * @import { Config } from './config.js' + * @import { Resolver } from './resolver.js' */ const EXIT_TOKEN_REGEXP = new RegExp(/EXIT([0-9]{3})/); const EXIT_TOKEN_LENGTH = 7; +/** + * @param {Config | null} config + * @returns {Promise} + */ +export function isAlive(config) { + return forwardCommandToDaemon(config, PING_COMMAND) + .then(() => true) + .catch(() => false); +} + +/** + * @param {Config | null} config + * @param {string} command + * @returns {Promise} + */ +export async function forwardCommandToDaemon(config, command) { + if (!config) { + return Promise.reject(new Error('config not found')); + } + + const socket = await connectToDaemon(config); + socket.write(JSON.stringify([config.token, command])); + socket.end(); + + return new Promise((resolve, reject) => { + socket.on('end', () => resolve()).on('error', (err) => reject(err)); + }); +} + /** * @param {Resolver} resolver * @param {Config} config @@ -38,9 +68,24 @@ export async function forwardToDaemon(resolver, config) { ); } - const socket = net.connect(config.port, '127.0.0.1'); + let socket; + try { + socket = await connectToDaemon(config); + } catch (/** @type {any} */ err) { + if (err['code'] === 'ECONNREFUSED') { + console.error(`eslint_d: ${err} - removing config`); + await removeConfig(resolver); + } else { + console.error(`eslint_d: ${err}`); + } + // eslint-disable-next-line require-atomic-updates + process.exitCode = 1; + return; + } + const args = [ config.token, + LINT_COMMAND, stdout ? stdout.level : 0, process.cwd(), eslint_args @@ -90,13 +135,8 @@ export async function forwardToDaemon(resolver, config) { process.exitCode = 1; } }) - .on('error', async (err) => { - if (err['code'] === 'ECONNREFUSED') { - console.error(`eslint_d: ${err} - removing config`); - await removeConfig(resolver); - } else { - console.error(`eslint_d: ${err}`); - } + .on('error', (err) => { + console.error(`eslint_d: ${err}`); process.exitCode = 1; }); @@ -113,6 +153,22 @@ export async function forwardToDaemon(resolver, config) { } } +/** + * @param {Config} config + * @returns {Promise} + */ +function connectToDaemon(config) { + return new Promise((resolve, reject) => { + const socket = net.connect(config.port, '127.0.0.1'); + socket + .on('connect', () => resolve(socket)) + .on('connectionAttemptFailed', (err) => { + reject(err); + socket.end(); + }); + }); +} + function readStdin() { return new Promise((resolve, reject) => { let content = ''; diff --git a/lib/forwarder.test.js b/lib/forwarder.test.js index ea4ec28..0819cb3 100644 --- a/lib/forwarder.test.js +++ b/lib/forwarder.test.js @@ -3,8 +3,9 @@ import { PassThrough } from 'node:stream'; import fs from 'node:fs/promises'; import supportsColor from 'supports-color'; import { assert, refute, match, sinon } from '@sinonjs/referee-sinon'; -import { forwardToDaemon } from './forwarder.js'; +import { forwardToDaemon, isAlive } from './forwarder.js'; import { createResolver } from './resolver.js'; +import { LINT_COMMAND } from './commands.js'; describe('lib/forwarder', () => { const resolver = createResolver(); @@ -38,6 +39,39 @@ describe('lib/forwarder', () => { sinon.replace(process, 'argv', argv); }); + context('isAlive', () => { + it('connects to 127.0.0.1 on port from config', () => { + isAlive(config); + + assert.calledOnceWith(net.connect, config.port, '127.0.0.1'); + }); + + it('handles response without errors', async () => { + const chunks = []; + sinon.replace( + socket, + 'read', + sinon.fake(() => (chunks.length ? chunks.shift() : null)) + ); + + const result = isAlive(config); + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); + socket.on.getCall(2).callback(); // readable + + assert.equals(await result, true); + }); + + it('handles ECONNREFUSED', async () => { + const result = isAlive(config); + const err = new Error('Connection refused'); + err['code'] = 'ECONNREFUSED'; + socket.on.getCall(1).callback(err); // error + + assert.equals(await result, false); + }); + }); + context('forwardToDaemon', () => { it('connects to 127.0.0.1 on port from config', () => { forwardToDaemon(resolver, config); @@ -45,14 +79,16 @@ describe('lib/forwarder', () => { assert.calledOnceWith(net.connect, config.port, '127.0.0.1'); }); - it('writes token, color level, cwd and argv to socket', () => { + it('writes token, color level, cwd and argv to socket', async () => { sinon.replace(process, 'cwd', sinon.fake.returns('the/cwd')); forwardToDaemon(resolver, config); + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); assert.calledOnceWith( socket.write, - `["token",${color_level},"the/cwd",[${JSON.stringify(process.argv0)},"eslint_d"]]` + `["token","${LINT_COMMAND}",${color_level},"the/cwd",[${JSON.stringify(process.argv0)},"eslint_d"]]` ); assert.calledOnce(socket.end); }); @@ -83,13 +119,15 @@ describe('lib/forwarder', () => { forwardToDaemon(resolver, config); await new Promise(setImmediate); + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); assert.calledThrice(socket.write); assert.calledWith(socket.write, '\n'); assert.calledWith(socket.write, 'text from stdin'); }); - it('forwards socket response to stdout', () => { + it('forwards socket response to stdout', async () => { const chunks = ['response ', 'from daemon']; sinon.replace( socket, @@ -99,8 +137,10 @@ describe('lib/forwarder', () => { sinon.replace(process.stdout, 'write', sinon.fake()); forwardToDaemon(resolver, config); - socket.on.firstCall.callback(); // readable - socket.on.secondCall.callback(); // end + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); + socket.on.getCall(2).callback(); // readable + socket.on.getCall(3).callback(); // end assert.calledThrice(process.stdout.write); assert.calledWith(process.stdout.write, 're'); @@ -108,7 +148,7 @@ describe('lib/forwarder', () => { assert.calledWith(process.stdout.write, ' daemon'); }); - it('handles "EXIT000" from response', () => { + it('handles "EXIT000" from response', async () => { const chunks = ['response from daemonEXIT000']; sinon.replace( socket, @@ -118,15 +158,17 @@ describe('lib/forwarder', () => { sinon.replace(process.stdout, 'write', sinon.fake()); forwardToDaemon(resolver, config); - socket.on.firstCall.callback(); // readable - socket.on.secondCall.callback(); // end + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); + socket.on.getCall(2).callback(); // readable + socket.on.getCall(3).callback(); // end assert.calledWith(process.stdout.write, 'response from daemon'); assert.equals(process.exitCode, 0); refute.called(console.error); }); - it('handles "EXIT001" from response', () => { + it('handles "EXIT001" from response', async () => { const chunks = ['response from daemonEXIT001']; sinon.replace( socket, @@ -136,15 +178,17 @@ describe('lib/forwarder', () => { sinon.replace(process.stdout, 'write', sinon.fake()); forwardToDaemon(resolver, config); - socket.on.firstCall.callback(); // readable - socket.on.secondCall.callback(); // end + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); + socket.on.getCall(2).callback(); // readable + socket.on.getCall(3).callback(); // end assert.calledWith(process.stdout.write, 'response from daemon'); assert.equals(process.exitCode, 1); refute.called(console.error); }); - it('handles "EXIT123" from response', () => { + it('handles "EXIT123" from response', async () => { const chunks = ['response from daemonEXIT123']; sinon.replace( socket, @@ -154,15 +198,17 @@ describe('lib/forwarder', () => { sinon.replace(process.stdout, 'write', sinon.fake()); forwardToDaemon(resolver, config); - socket.on.firstCall.callback(); // readable - socket.on.secondCall.callback(); // end + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); + socket.on.getCall(2).callback(); // readable + socket.on.getCall(3).callback(); // end assert.calledWith(process.stdout.write, 'response from daemon'); assert.equals(process.exitCode, 123); refute.called(console.error); }); - it('handles "EXIT001" inside response', () => { + it('handles "EXIT001" inside response', async () => { const chunks = ['response EXIT001', ' from daemonEXIT002']; sinon.replace( socket, @@ -172,8 +218,10 @@ describe('lib/forwarder', () => { sinon.replace(process.stdout, 'write', sinon.fake()); forwardToDaemon(resolver, config); - socket.on.firstCall.callback(); // readable - socket.on.secondCall.callback(); // end + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); + socket.on.getCall(2).callback(); // readable + socket.on.getCall(3).callback(); // end assert.calledWith(process.stdout.write, 'response '); assert.calledWith(process.stdout.write, 'EXIT001 from daemon'); @@ -181,7 +229,7 @@ describe('lib/forwarder', () => { refute.called(console.error); }); - it('logs error and sets exitCode to 1 if response does not end with EXIT marker', () => { + it('logs error and sets exitCode to 1 if response does not end with EXIT marker', async () => { const chunks = ['response from daemon']; sinon.replace( socket, @@ -191,8 +239,10 @@ describe('lib/forwarder', () => { sinon.replace(process.stdout, 'write', sinon.fake()); forwardToDaemon(resolver, config); - socket.on.firstCall.callback(); // readable - socket.on.secondCall.callback(); // end + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); + socket.on.getCall(2).callback(); // readable + socket.on.getCall(3).callback(); // end assert.calledWith(process.stdout.write, 'response from'); assert.calledWith(process.stdout.write, ' daemon'); @@ -200,24 +250,27 @@ describe('lib/forwarder', () => { assert.calledOnceWith(console.error, 'eslint_d: unexpected response'); }); - it('logs error from stream', () => { + it('logs error from stream', async () => { sinon.replace(fs, 'unlink', sinon.fake.resolves()); forwardToDaemon(resolver, config); - socket.on.thirdCall.callback(new Error('Ouch!')); // error + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); + socket.on.getCall(4).callback(new Error('Ouch!')); // error assert.calledOnceWith(console.error, 'eslint_d: Error: Ouch!'); assert.equals(process.exitCode, 1); refute.called(fs.unlink); }); - it('logs ECONNREFUSED error from stream and removes config', () => { + it('logs ECONNREFUSED error from stream and removes config', async () => { sinon.replace(fs, 'unlink', sinon.fake.resolves()); forwardToDaemon(resolver, config); const err = new Error('Connection refused'); err['code'] = 'ECONNREFUSED'; - socket.on.thirdCall.callback(err); // error + socket.on.getCall(1).callback(err); // connectionAttemptFailed + await new Promise(setImmediate); assert.calledOnceWith( console.error, @@ -250,11 +303,13 @@ describe('lib/forwarder', () => { forwardToDaemon(resolver, config); await new Promise(setImmediate); + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); assert.calledThrice(socket.write); assert.calledWith( socket.write, - `["token",${color_level},"cwd",[${JSON.stringify(process.argv0)},"eslint_d","--stdin","--fix-dry-run","--format","json","--other","--options"]]` + `["token","${LINT_COMMAND}",${color_level},"cwd",[${JSON.stringify(process.argv0)},"eslint_d","--stdin","--fix-dry-run","--format","json","--other","--options"]]` ); assert.calledWith(socket.write, '\n'); assert.calledWith(socket.write, 'text from stdin'); @@ -273,8 +328,10 @@ describe('lib/forwarder', () => { forwardToDaemon(resolver, config); await new Promise(setImmediate); - socket.on.firstCall.callback(); // readable - socket.on.secondCall.callback(); // end + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); + socket.on.getCall(2).callback(); // readable + socket.on.getCall(3).callback(); // end assert.calledWith(process.stdout.write, 'response from daemon'); assert.equals(process.exitCode, 0); @@ -293,8 +350,10 @@ describe('lib/forwarder', () => { forwardToDaemon(resolver, config); await new Promise(setImmediate); - socket.on.firstCall.callback(); // readable - socket.on.secondCall.callback(); // end + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); + socket.on.getCall(2).callback(); // readable + socket.on.getCall(3).callback(); // end assert.calledWith(process.stdout.write, 'text from stdin'); assert.equals(process.exitCode, 0); @@ -313,8 +372,10 @@ describe('lib/forwarder', () => { forwardToDaemon(resolver, config); await new Promise(setImmediate); - socket.on.firstCall.callback(); // readable - socket.on.secondCall.callback(); // end + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); + socket.on.getCall(2).callback(); // readable + socket.on.getCall(3).callback(); // end assert.calledWith(process.stdout.write, 'text from stdin'); assert.equals(process.exitCode, 1); @@ -339,8 +400,10 @@ describe('lib/forwarder', () => { forwardToDaemon(resolver, config); await new Promise(setImmediate); - socket.on.firstCall.callback(); // readable - socket.on.secondCall.callback(); // end + socket.on.getCall(0).callback(); // connect + await new Promise(setImmediate); + socket.on.getCall(2).callback(); // readable + socket.on.getCall(3).callback(); // end assert.calledWith(process.stdout.write, 'response from daemon'); assert.equals(process.exitCode, 1); diff --git a/lib/launcher.js b/lib/launcher.js index dfe62fd..c7db4a2 100644 --- a/lib/launcher.js +++ b/lib/launcher.js @@ -1,8 +1,9 @@ import fs from 'node:fs'; -import net from 'node:net'; import os from 'node:os'; import child_process from 'node:child_process'; import { loadConfig, removeConfig } from './config.js'; +import { forwardCommandToDaemon } from './forwarder.js'; +import { SHUTDOWN_COMMAND } from './commands.js'; /** * @import { Config } from './config.js' @@ -64,12 +65,7 @@ export async function stopDaemon(resolver, config) { */ function platformAwareStopDaemon(config) { if (os.platform() === 'win32') { - return new Promise((resolve, reject) => { - const socket = net.connect(config.port, '127.0.0.1'); - socket.once('error', reject); - socket.write(JSON.stringify([config.token, 'ESLINT_D_STOP'])); - socket.end(() => resolve(undefined)); - }); + return forwardCommandToDaemon(config, SHUTDOWN_COMMAND); } process.kill(config.pid, 'SIGTERM'); diff --git a/lib/launcher.test.js b/lib/launcher.test.js index d28229a..129b810 100644 --- a/lib/launcher.test.js +++ b/lib/launcher.test.js @@ -8,6 +8,7 @@ import { assert, refute, sinon } from '@sinonjs/referee-sinon'; import { createResolver } from './resolver.js'; import { launchDaemon, stopDaemon } from './launcher.js'; import { strictEqual } from 'node:assert'; +import { SHUTDOWN_COMMAND } from './commands.js'; describe('lib/launcher', () => { const resolver = createResolver(); @@ -287,7 +288,7 @@ describe('lib/launcher', () => { data += buf.toString(); }) .on('end', () => { - strictEqual(data, '["token","ESLINT_D_STOP"]'); + strictEqual(data, `["token","${SHUTDOWN_COMMAND}"]`); done(); }); }); diff --git a/lib/service.js b/lib/service.js index 622c674..150aa24 100644 --- a/lib/service.js +++ b/lib/service.js @@ -1,4 +1,5 @@ import { createRequire } from 'node:module'; +import { LINT_COMMAND, PING_COMMAND, SHUTDOWN_COMMAND } from './commands.js'; /** * @import { Socket } from 'node:net' @@ -40,14 +41,14 @@ export function createService(resolver, token, shutdown) { text = content.slice(newline + 1); content = content.slice(0, newline); } - const [request_token, color_level, cwd, argv] = JSON.parse(content); + const [request_token, command, color_level, cwd, argv] = + JSON.parse(content); if (request_token !== token) { con.end(); return; } - if (color_level === 'ESLINT_D_STOP') { - shutdown(); - con.end(); + if (command !== LINT_COMMAND) { + onCommand(con, command); return; } @@ -74,4 +75,23 @@ export function createService(resolver, token, shutdown) { process.stderr.write = stderr_write; }); }; + + /** + * @param {Socket} con + * @param {string} command + */ + function onCommand(con, command) { + switch (command) { + case SHUTDOWN_COMMAND: + shutdown(); + break; + case PING_COMMAND: + break; + default: + con.write(`Unknown command: ${command}`); + } + + con.end(); + con.destroySoon(); + } } diff --git a/lib/service.test.js b/lib/service.test.js index 2e31bf5..381f35b 100644 --- a/lib/service.test.js +++ b/lib/service.test.js @@ -2,6 +2,7 @@ import { Socket } from 'node:net'; import { assert, refute, sinon } from '@sinonjs/referee-sinon'; import { createResolver } from './resolver.js'; import { createService } from './service.js'; +import { LINT_COMMAND, SHUTDOWN_COMMAND } from './commands.js'; describe('lib/service', () => { context('createService', () => { @@ -41,14 +42,15 @@ describe('lib/service', () => { /** * @param {string} request_token + * @param {string} command * @param {string} color_level * @param {string} cwd * @param {string[]} argv * @param {string} [text] */ - function send(request_token, color_level, cwd, argv, text) { + function send(request_token, command, color_level, cwd, argv, text) { const chunks = [ - `["${request_token}",${color_level},`, + `["${request_token}","${command}",${color_level},`, `${JSON.stringify(cwd)},${JSON.stringify(argv)}]` ]; if (text !== undefined) { @@ -69,7 +71,7 @@ describe('lib/service', () => { }); it('immediately ends connection if token does not match', () => { - send('invalid', '0', '/', []); + send('invalid', LINT_COMMAND, '0', '/', []); assert.calledOnceWithExactly(con.end); refute.called(con.write); @@ -78,7 +80,7 @@ describe('lib/service', () => { }); it('sets chalk.level to given color and changes directory', async () => { - send(token, '3', '/', []); + send(token, LINT_COMMAND, '3', '/', []); await eslint_promise.resolve(0); assert.equals(chalk.level, 3); @@ -86,7 +88,7 @@ describe('lib/service', () => { }); it('replaces stdout with a function that write to the socket', async () => { - send(token, '3', '/', []); + send(token, LINT_COMMAND, '3', '/', []); process.stdout.write('test'); await eslint_promise.resolve(0); @@ -94,7 +96,7 @@ describe('lib/service', () => { }); it('replaces stderr with a function that write to the socket', async () => { - send(token, '3', '/', []); + send(token, LINT_COMMAND, '3', '/', []); process.stderr.write('test'); await eslint_promise.resolve(0); @@ -102,21 +104,21 @@ describe('lib/service', () => { }); it('invokes eslint.execute with given args and no text', async () => { - send(token, '3', '/', ['--fix']); + send(token, LINT_COMMAND, '3', '/', ['--fix']); await eslint_promise.resolve(0); assert.calledOnceWith(eslint.execute, ['--fix'], null, true); }); it('invokes eslint.execute with given text', async () => { - send(token, '3', '/', [], 'some text'); + send(token, LINT_COMMAND, '3', '/', [], 'some text'); await eslint_promise.resolve(0); assert.calledOnceWith(eslint.execute, [], 'some text', true); }); it('ends connection with "EXIT000" if eslint returns 0', async () => { - send(token, '3', '/', []); + send(token, LINT_COMMAND, '3', '/', []); await eslint_promise.resolve(0); refute.called(con.write); @@ -124,7 +126,7 @@ describe('lib/service', () => { }); it('ends connection with "EXIT001" if eslint returns 1', async () => { - send(token, '3', '/', []); + send(token, LINT_COMMAND, '3', '/', []); await eslint_promise.resolve(1); refute.called(con.write); @@ -132,7 +134,7 @@ describe('lib/service', () => { }); it('ends connection with "EXIT002" if eslint returns 2', async () => { - send(token, '3', '/', []); + send(token, LINT_COMMAND, '3', '/', []); await eslint_promise.resolve(2); refute.called(con.write); @@ -140,7 +142,7 @@ describe('lib/service', () => { }); it('ends connection with "EXIT123" if eslint returns 123', async () => { - send(token, '3', '/', []); + send(token, LINT_COMMAND, '3', '/', []); await eslint_promise.resolve(123); refute.called(con.write); @@ -148,15 +150,15 @@ describe('lib/service', () => { }); it('ends connection with "EXIT001" if eslint throws', async () => { - send(token, '3', '/', []); + send(token, LINT_COMMAND, '3', '/', []); await eslint_promise.reject(new Error('Ouch!')); assert.calledOnceWith(con.write, 'Error: Ouch!'); assert.calledOnceWith(con.end, 'EXIT001'); }); - it('shutdown daemon if ESLINT_D_STOP received', async () => { - send(token, '"ESLINT_D_STOP"', '/', []); + it('shutdown daemon if shutdown command received', async () => { + send(token, SHUTDOWN_COMMAND, '3', '/', []); await shutdown_promise; }); diff --git a/lib/status.js b/lib/status.js index 7908d77..255da71 100644 --- a/lib/status.js +++ b/lib/status.js @@ -1,3 +1,5 @@ +import { isAlive } from './forwarder.js'; + /** * @import { Resolver } from './resolver.js' * @import { Config } from './config.js' @@ -7,8 +9,10 @@ * @param {Resolver} resolver * @param {Config | null} config */ -export function status(resolver, config) { - const running = config ? `Running (${config.pid})` : 'Not running'; +export async function status(resolver, config) { + const isRunning = config ? await isAlive(config) : false; + const running = + config && isRunning ? `Running (${config.pid})` : 'Not running'; const eslint_origin = resolver.bundled ? 'bundled' : 'local'; const eslint_version = resolver.require( `${resolver.base}/package.json` diff --git a/lib/status.test.js b/lib/status.test.js index 2ad7e09..142eb85 100644 --- a/lib/status.test.js +++ b/lib/status.test.js @@ -1,21 +1,48 @@ +import net from 'node:net'; +import { PassThrough } from 'node:stream'; import { assert, sinon } from '@sinonjs/referee-sinon'; import { status } from './status.js'; describe('lib/status', () => { + let socket; const config = { token: 'token', port: 123, pid: 456, hash: 'hash' }; beforeEach(() => { + socket = new PassThrough(); + sinon.replace(socket, 'write', sinon.fake()); + sinon.replace(socket, 'end', sinon.fake()); + sinon.replace(net, 'connect', sinon.fake.returns(socket)); sinon.replace(console, 'log', sinon.fake()); }); - it('prints status when running and bundled', () => { + /** + * @param {boolean} isAlive + */ + function setDaemonState(isAlive) { + sinon.replace( + socket, + 'on', + sinon.fake((event, cb) => { + if (isAlive && event !== 'error') { + cb(); + } + if (!isAlive && event === 'error') { + cb(); + } + return socket; + }) + ); + } + + it('prints status when running and bundled', async () => { + setDaemonState(true); const resolver = { base: '/path/to/eslint', bundled: true, require: sinon.fake.returns({ version: '1.2.3' }) }; - status(resolver, config); + await status(resolver, config); assert.calledOnceWith( console.log, @@ -23,14 +50,15 @@ describe('lib/status', () => { ); }); - it('prints status when running and local', () => { + it('prints status when running and local', async () => { + setDaemonState(true); const resolver = { base: '/path/to/eslint', bundled: false, require: sinon.fake.returns({ version: '1.2.3' }) }; - status(resolver, config); + await status(resolver, config); assert.calledOnceWith( console.log, @@ -38,14 +66,15 @@ describe('lib/status', () => { ); }); - it('prints status when not running and bundled', () => { + it('prints status when not running and bundled', async () => { + setDaemonState(false); const resolver = { base: '/path/to/eslint', bundled: true, require: sinon.fake.returns({ version: '1.2.3' }) }; - status(resolver, null); + await status(resolver, null); assert.calledOnceWith( console.log, @@ -53,14 +82,15 @@ describe('lib/status', () => { ); }); - it('prints status when not running and local', () => { + it('prints status when not running and local', async () => { + setDaemonState(false); const resolver = { base: '/path/to/eslint', bundled: false, require: sinon.fake.returns({ version: '1.2.3' }) }; - status(resolver, null); + await status(resolver, null); assert.calledOnceWith( console.log,