Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increase test coverage, including incrementNodeInspectorPort #1428

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 6 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

const EventEmitter = require('events').EventEmitter;
const spawn = require('child_process').spawn;
const childProcess = require('child_process');
const path = require('path');
const fs = require('fs');

Expand Down Expand Up @@ -1335,15 +1335,15 @@ class Command extends EventEmitter {
// add executable arguments to spawn
args = incrementNodeInspectorPort(process.execArgv).concat(args);

proc = spawn(process.argv[0], args, { stdio: 'inherit' });
proc = childProcess.spawn(process.argv[0], args, { stdio: 'inherit' });
} else {
proc = spawn(bin, args, { stdio: 'inherit' });
proc = childProcess.spawn(bin, args, { stdio: 'inherit' });
}
} else {
args.unshift(bin);
// add executable arguments to spawn
args = incrementNodeInspectorPort(process.execArgv).concat(args);
proc = spawn(process.execPath, args, { stdio: 'inherit' });
proc = childProcess.spawn(process.execPath, args, { stdio: 'inherit' });
}

const signals = ['SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGINT', 'SIGHUP'];
Expand Down Expand Up @@ -1672,13 +1672,8 @@ class Command extends EventEmitter {
* @api private
*/

optionMissingArgument(option, flag) {
let message;
if (flag) {
message = `error: option '${option.flags}' argument missing, got '${flag}'`;
} else {
message = `error: option '${option.flags}' argument missing`;
}
optionMissingArgument(option) {
const message = `error: option '${option.flags}' argument missing`;
this._displayError(1, 'commander.optionMissingArgument', message);
};

Expand Down
7 changes: 7 additions & 0 deletions tests/command.addHelpText.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ describe('program calls to addHelpText', () => {
expect(writeSpy).toHaveBeenNthCalledWith(4, 'after\n');
expect(writeSpy).toHaveBeenNthCalledWith(5, 'afterAll\n');
});

test('when "silly" position then throw', () => {
const program = new commander.Command();
expect(() => {
program.addHelpText('silly', 'text');
}).toThrow();
});
});

describe('program and subcommand calls to addHelpText', () => {
Expand Down
9 changes: 9 additions & 0 deletions tests/command.alias.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,12 @@ test('when set aliases then can get aliases', () => {
program.aliases(aliases);
expect(program.aliases()).toEqual(aliases);
});

test('when set alias on executable then can get alias', () => {
const program = new commander.Command();
const alias = 'abcde';
program
.command('external', 'external command')
.alias(alias);
expect(program.commands[0].alias()).toEqual(alias);
});
23 changes: 23 additions & 0 deletions tests/command.exitOverride.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,26 @@ describe('.exitOverride and error details', () => {
expectCommanderError(caughtErr, 1, 'commander.invalidOptionArgument', "error: option '--colour <shade>' argument 'green' is invalid. NO");
});
});

test('when no override and error then exit(1)', () => {
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => { });
const program = new commander.Command();
program.configureOutput({ outputError: () => {} });
program.parse(['--unknownOption'], { from: 'user' });
expect(exitSpy).toHaveBeenCalledWith(1);
exitSpy.mockRestore();
});

test('when custom processing throws custom error then throw custom error', () => {
function justSayNo(value) {
throw new Error('custom');
}
const program = new commander.Command();
program
.exitOverride()
.option('-s, --shade <value>', 'specify shade', justSayNo);

expect(() => {
program.parse(['--shade', 'green'], { from: 'user' });
}).toThrow('custom');
});
7 changes: 7 additions & 0 deletions tests/command.help.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ test('when call outputHelp(cb) then display cb output', () => {
writeSpy.mockClear();
});

test('when call deprecated outputHelp(cb) with wrong callback return type then throw', () => {
const program = new commander.Command();
expect(() => {
program.outputHelp((helpInformation) => 3);
}).toThrow();
});

test('when command sets deprecated noHelp then not displayed in helpInformation', () => {
const program = new commander.Command();
program
Expand Down
26 changes: 26 additions & 0 deletions tests/command.helpCommand.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@ describe('help command listed in helpInformation', () => {
expect(helpInformation).toMatch(/help \[command\]/);
});

test('when program has subcommands and specify only unknown option then display help', () => {
const program = new commander.Command();
program
.configureHelp({ formatHelp: () => '' })
.exitOverride()
.allowUnknownOption()
.command('foo');
let caughtErr;
try {
program.parse(['--unknown'], { from: 'user' });
} catch (err) {
caughtErr = err;
}
expect(caughtErr.code).toEqual('commander.help');
});

test('when program has subcommands and suppress help command then no help command', () => {
const program = new commander.Command();
program.addHelpCommand(false);
Expand Down Expand Up @@ -67,6 +83,16 @@ describe('help command processed on correct command', () => {
}).toThrow('program');
});

test('when "program help unknown" then program', () => {
const program = new commander.Command();
program.exitOverride();
program.command('sub1');
program.exitOverride(() => { throw new Error('program'); });
expect(() => {
program.parse('node test.js help unknown'.split(' '));
}).toThrow('program');
});

test('when "program help sub1" then sub1', () => {
const program = new commander.Command();
program.exitOverride();
Expand Down
28 changes: 26 additions & 2 deletions tests/command.parse.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const commander = require('../');
// https://github.com/electron/electron/issues/4690#issuecomment-217435222
// https://www.electronjs.org/docs/api/process#processdefaultapp-readonly

describe('.parse() user args', () => {
describe('.parse() args from', () => {
test('when no args then use process.argv and app/script/args', () => {
const program = new commander.Command();
const hold = process.argv;
Expand All @@ -14,7 +14,16 @@ describe('.parse() user args', () => {
expect(program.args).toEqual(['user']);
});

// implicit also supports detecting electron but more implementation knowledge required than useful to test
test('when no args and electron properties and not default app then use process.argv and app/args', () => {
const program = new commander.Command();
const holdArgv = process.argv;
process.versions.electron = '1.2.3';
process.argv = 'node user'.split(' ');
program.parse();
delete process.versions.electron;
process.argv = holdArgv;
expect(program.args).toEqual(['user']);
});

test('when args then app/script/args', () => {
const program = new commander.Command();
Expand Down Expand Up @@ -51,6 +60,13 @@ describe('.parse() user args', () => {
program.parse('user'.split(' '), { from: 'user' });
expect(program.args).toEqual(['user']);
});

test('when args from "silly" then throw', () => {
const program = new commander.Command();
expect(() => {
program.parse(['node', 'script.js'], { from: 'silly' });
}).toThrow();
});
});

describe('return type', () => {
Expand All @@ -72,3 +88,11 @@ describe('return type', () => {
expect(result).toBe(program);
});
});

// Easy mistake to make when writing unit tests
test('when parse strings instead of array then throw', () => {
const program = new commander.Command();
expect(() => {
program.parse('node', 'test');
}).toThrow();
});
16 changes: 16 additions & 0 deletions tests/command.unknownCommand.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,20 @@ describe('unknownOption', () => {
}
expect(caughtErr.code).toBe('commander.unknownCommand');
});

test('when unknown subcommand then help suggestion includes command path', () => {
const program = new commander.Command();
program
.exitOverride()
.command('sub')
.command('subsub');
let caughtErr;
try {
program.parse('node test.js sub unknown'.split(' '));
} catch (err) {
caughtErr = err;
}
expect(caughtErr.code).toBe('commander.unknownCommand');
expect(writeErrorSpy.mock.calls[0][0]).toMatch('test sub');
});
});
8 changes: 8 additions & 0 deletions tests/commander.configureCommand.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,11 @@ test('when storeOptionsAsProperties(false) then opts+command passed to action',
program.parse(['node', 'test', 'value']);
expect(callback).toHaveBeenCalledWith('value', program.opts(), program);
});

test('when storeOptionsAsProperties() after adding option then throw', () => {
const program = new commander.Command();
program.option('--port <number>', 'port number', '80');
expect(() => {
program.storeOptionsAsProperties(false);
}).toThrow();
});
135 changes: 135 additions & 0 deletions tests/incrementNodeInspectorPort.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
const childProcess = require('child_process');
const path = require('path');
const commander = require('../');

describe('incrementNodeInspectorPort', () => {
let spawnSpy;
let signalSpy;
const oldExecArgv = process.execArgv;

beforeAll(() => {
spawnSpy = jest.spyOn(childProcess, 'spawn').mockImplementation(() => {
return {
on: () => {}
};
});
signalSpy = jest.spyOn(process, 'on').mockImplementation(() => {});
});

afterEach(() => {
spawnSpy.mockClear();
});

afterAll(() => {
spawnSpy.mockRestore();
signalSpy.mockRestore();
process.execArgv = oldExecArgv;
});

function makeProgram() {
const program = new commander.Command();
const fileWhichExists = path.join(__dirname, './fixtures/pm-cache.js');
program.command('cache', 'stand-alone command', { executableFile: fileWhichExists });
return program;
}

function extractMockExecArgs(mock) {
return mock.mock.calls[0][1].slice(0, -1);
}

test('when --inspect then bump port', () => {
const program = makeProgram();
process.execArgv = ['--inspect'];
program.parse(['node', 'test', 'cache']);
const execArgs = extractMockExecArgs(spawnSpy);
expect(execArgs).toEqual(['--inspect=127.0.0.1:9230']);
});

test('when --inspect=100 then bump port', () => {
const program = makeProgram();
process.execArgv = ['--inspect=100'];
program.parse(['node', 'test', 'cache']);
const execArgs = extractMockExecArgs(spawnSpy);
expect(execArgs).toEqual(['--inspect=127.0.0.1:101']);
});

test('when --inspect=1.2.3.4:100 then bump port', () => {
const program = makeProgram();
process.execArgv = ['--inspect=1.2.3.4:100'];
program.parse(['node', 'test', 'cache']);
const execArgs = extractMockExecArgs(spawnSpy);
expect(execArgs).toEqual(['--inspect=1.2.3.4:101']);
});

test('when --inspect=1.2.3.4 then bump port', () => {
const program = makeProgram();
process.execArgv = ['--inspect=1.2.3.4'];
program.parse(['node', 'test', 'cache']);
const execArgs = extractMockExecArgs(spawnSpy);
expect(execArgs).toEqual(['--inspect=1.2.3.4:9230']);
});

test('when --inspect-brk then bump port', () => {
const program = makeProgram();
process.execArgv = ['--inspect-brk'];
program.parse(['node', 'test', 'cache']);
const execArgs = extractMockExecArgs(spawnSpy);
expect(execArgs).toEqual(['--inspect-brk=127.0.0.1:9230']);
});

test('when --inspect-brk=100 then bump port', () => {
const program = makeProgram();
process.execArgv = ['--inspect-brk=100'];
program.parse(['node', 'test', 'cache']);
const execArgs = extractMockExecArgs(spawnSpy);
expect(execArgs).toEqual(['--inspect-brk=127.0.0.1:101']);
});

test('when --inspect-brk=1.2.3.4 then bump port', () => {
const program = makeProgram();
process.execArgv = ['--inspect-brk=1.2.3.4'];
program.parse(['node', 'test', 'cache']);
const execArgs = extractMockExecArgs(spawnSpy);
expect(execArgs).toEqual(['--inspect-brk=1.2.3.4:9230']);
});

test('when --inspect-brk=1.2.3.4:100 then bump port', () => {
const program = makeProgram();
process.execArgv = ['--inspect-brk=1.2.3.4:100'];
program.parse(['node', 'test', 'cache']);
const execArgs = extractMockExecArgs(spawnSpy);
expect(execArgs).toEqual(['--inspect-brk=1.2.3.4:101']);
});

test('when --inspect-port=100 then bump port', () => {
const program = makeProgram();
process.execArgv = ['--inspect-port=100'];
program.parse(['node', 'test', 'cache']);
const execArgs = extractMockExecArgs(spawnSpy);
expect(execArgs).toEqual(['--inspect-port=127.0.0.1:101']);
});

test('when --inspect-port=1.2.3.4:100 then bump port', () => {
const program = makeProgram();
process.execArgv = ['--inspect-port=1.2.3.4:100'];
program.parse(['node', 'test', 'cache']);
const execArgs = extractMockExecArgs(spawnSpy);
expect(execArgs).toEqual(['--inspect-port=1.2.3.4:101']);
});

test('when --inspect-unexpected then unchanged', () => {
const program = makeProgram();
process.execArgv = ['--inspect-unexpected'];
program.parse(['node', 'test', 'cache']);
const execArgs = extractMockExecArgs(spawnSpy);
expect(execArgs).toEqual(['--inspect-unexpected']);
});

test('when --frozen-intrinsics then unchanged', () => {
const program = makeProgram();
process.execArgv = ['--frozen-intrinsics '];
program.parse(['node', 'test', 'cache']);
const execArgs = extractMockExecArgs(spawnSpy);
expect(execArgs).toEqual(['--frozen-intrinsics ']);
});
});