eslint_d
Version:
Speed up eslint to accelerate your development workflow
290 lines (234 loc) • 8.98 kB
JavaScript
import { createRequire } from 'node:module';
import { Socket } from 'node:net';
import debug from 'debug';
import { assert, refute, sinon } from '@sinonjs/referee-sinon';
import { createResolver } from './resolver.js';
import { createService, importChalk, eslintMajorVersion } from './service.js';
import { LINT_COMMAND, SHUTDOWN_COMMAND } from './commands.js';
describe('lib/service', () => {
context('createService', () => {
const original_stdout_write = process.stdout.write;
const original_stderr_write = process.stderr.write;
const resolver = createResolver();
if (resolver === 'fail' || resolver === 'ignore') {
throw new Error('Failed to create resolver');
}
const eslint = resolver.require(`${resolver.base}/lib/cli.js`);
const require = createRequire(`${resolver.base}/`);
const majorVersion = eslintMajorVersion(resolver, require);
const useChalk = majorVersion < 10;
const chalk = importChalk(resolver, require);
const token = 'token';
let shutdown_promise;
let eslint_promise;
let service;
let con;
function connect() {
con = new Socket({ allowHalfOpen: true });
sinon.replace(con, 'write', sinon.fake());
sinon.replace(con, 'end', sinon.fake());
service(con);
}
beforeEach(() => {
eslint_promise = sinon.promise();
shutdown_promise = sinon.promise();
sinon.replace(eslint, 'execute', sinon.fake.returns(eslint_promise));
sinon.replace(process, 'chdir', sinon.fake());
sinon.replace(debug, 'enable', sinon.fake());
sinon.replace(debug, 'disable', sinon.fake());
service = createService(resolver, token, () =>
shutdown_promise.resolve()
);
if (useChalk) {
chalk.level = '-';
}
connect();
});
afterEach(() => {
assert.same(process.stdout.write, original_stdout_write);
assert.same(process.stderr.write, original_stderr_write);
});
it('does not enable debug by default', () => {
refute.called(debug.enable);
});
it('enables debug with DEBUG environment variable', () => {
sinon.define(process.env, 'DEBUG', 'eslint_d:*');
service = createService(resolver, token, () =>
shutdown_promise.resolve()
);
assert.calledOnceWith(debug.enable, 'eslint_d:*');
});
/**
* @param {string} request_token
* @param {string} command
* @param {string} color_level
* @param {string} cwd
* @param {string[]} argv
* @param {string} [text]
* @param {string} [DEBUG]
*/
function send(
request_token,
command,
color_level,
cwd,
argv,
text,
DEBUG = ''
) {
const chunks = [
`["${request_token}","${command}",${color_level},`,
`${JSON.stringify(cwd)},${JSON.stringify(argv)},"${DEBUG}"]`
];
if (text !== undefined) {
chunks.push(`\n${text}`);
}
sinon.replace(con, 'read', () => chunks.shift() || null);
con.emit('readable');
con.emit('end');
}
it('immediately ends connection if no data is received', () => {
con.emit('end');
assert.calledOnceWithExactly(con.end);
refute.called(con.write);
refute.called(process.chdir);
if (useChalk) {
assert.equals(chalk.level, '-');
}
});
it('immediately ends connection if token does not match', () => {
send('invalid', LINT_COMMAND, '0', '/', []);
assert.calledOnceWithExactly(con.end);
refute.called(con.write);
refute.called(process.chdir);
if (useChalk) {
assert.equals(chalk.level, '-');
}
});
it('sets chalk.level to given color and changes directory', async () => {
send(token, LINT_COMMAND, '3', '/', []);
await eslint_promise.resolve(0);
if (majorVersion < 10) {
assert.equals(chalk.level, 3);
}
assert.calledOnceWith(process.chdir, '/');
});
it('replaces stdout with a function that write to the socket', async () => {
send(token, LINT_COMMAND, '3', '/', []);
process.stdout.write('test');
await eslint_promise.resolve(0);
assert.calledOnceWith(con.write, 'test');
});
it('replaces stderr with a function that write to the socket', async () => {
send(token, LINT_COMMAND, '3', '/', []);
process.stderr.write('test');
await eslint_promise.resolve(0);
assert.calledOnceWith(con.write, 'test');
});
it('invokes eslint.execute with given args and no text', async () => {
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, 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, LINT_COMMAND, '3', '/', []);
await eslint_promise.resolve(0);
refute.called(con.write);
assert.calledOnceWith(con.end, 'EXIT000');
});
it('ends connection with "EXIT001" if eslint returns 1', async () => {
send(token, LINT_COMMAND, '3', '/', []);
await eslint_promise.resolve(1);
refute.called(con.write);
assert.calledOnceWith(con.end, 'EXIT001');
});
it('ends connection with "EXIT002" if eslint returns 2', async () => {
send(token, LINT_COMMAND, '3', '/', []);
await eslint_promise.resolve(2);
refute.called(con.write);
assert.calledOnceWith(con.end, 'EXIT002');
});
it('ends connection with "EXIT123" if eslint returns 123', async () => {
send(token, LINT_COMMAND, '3', '/', []);
await eslint_promise.resolve(123);
refute.called(con.write);
assert.calledOnceWith(con.end, 'EXIT123');
});
it('ends connection with "EXIT001" if eslint throws', async () => {
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 shutdown command received', async () => {
send(token, SHUTDOWN_COMMAND, '3', '/', []);
await shutdown_promise;
});
it('does not enable or disable debug by default', async () => {
send(token, LINT_COMMAND, '3', '/', []);
await eslint_promise.resolve(0);
refute.called(debug.enable);
refute.called(debug.disable);
});
it('enables debug if DEBUG is passed', async () => {
send(token, LINT_COMMAND, '3', '/', [], undefined, 'eslint_d:*');
await eslint_promise.resolve(0);
assert.calledOnceWith(debug.enable, 'eslint_d:*');
refute.called(debug.disable);
});
it('disables debug if DEBUG is not passed on subsequent request', async () => {
send(token, LINT_COMMAND, '3', '/', [], undefined, 'eslint_d:*');
await eslint_promise.resolve(0);
debug.enable['resetHistory']();
connect();
send(token, LINT_COMMAND, '3', '/', []);
await new Promise(setImmediate);
assert.calledOnce(debug.disable);
refute.called(debug.enable);
});
it('does not enable debug if DEBUG is passed and global debug is on', async () => {
sinon.define(process.env, 'DEBUG', 'eslint_d:*');
service = createService(resolver, token, () =>
shutdown_promise.resolve()
);
debug.enable['resetHistory']();
connect();
send(token, LINT_COMMAND, '3', '/', [], undefined, 'eslint_d:*');
await eslint_promise.resolve(0);
refute.called(debug.enable);
refute.called(debug.disable);
});
it('enables debug if DEBUG is passed and global debug is different', async () => {
sinon.define(process.env, 'DEBUG', 'eslint_d:*');
service = createService(resolver, token, () =>
shutdown_promise.resolve()
);
debug.enable['resetHistory']();
connect();
send(token, LINT_COMMAND, '3', '/', [], undefined, 'eslint:*');
await eslint_promise.resolve(0);
assert.calledOnceWith(debug.enable, 'eslint:*');
refute.called(debug.disable);
});
it('resets debug to global is not passed on subsequent request', async () => {
sinon.define(process.env, 'DEBUG', 'eslint_d:*');
service = createService(resolver, token, () =>
shutdown_promise.resolve()
);
connect();
send(token, LINT_COMMAND, '3', '/', [], undefined, 'eslint:*');
await eslint_promise.resolve(0);
debug.enable['resetHistory']();
connect();
send(token, LINT_COMMAND, '3', '/', []);
await new Promise(setImmediate);
assert.calledOnceWith(debug.enable, 'eslint_d:*');
refute.called(debug.disable);
});
});
});