Skip to content

Commit

Permalink
feat: Add ability to display stacktraces from unhandled exceptions
Browse files Browse the repository at this point in the history
If the -v / --version flag is set and an unhandled error is thrown, the stacktract of that error
will be rendered.  Fixed some bugs related to some unhandled exceptions not being handled correctly.
Add .idea to .gitignore.

Fixes #166
  • Loading branch information
jhorbulyk committed Feb 17, 2020
1 parent 9282872 commit 5d2f935
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 15 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ coverage
docs
docs-src
local_tests
.idea
18 changes: 11 additions & 7 deletions lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -479,13 +479,17 @@ class Command extends GetterSetter {
this._program
));
}
const actionResults = this._action.apply(this, [args, options, this._logger]);
const response = Promise.resolve(actionResults);
return response
.catch(err => {
err = err instanceof Error ? err : new Error(err);
throw this._program.fatalError(err);
})
try {
const actionResults = this._action.apply(this, [args, options, this._logger]);
return Promise.resolve(actionResults)
.catch(err => {
err = err instanceof Error ? err : new Error(err);
throw this._program.fatalError(err);
})
} catch (err) {
const e = err instanceof Error ? err : new Error(err);
throw this._program.fatalError(e);
}
}

/**
Expand Down
7 changes: 6 additions & 1 deletion lib/program.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ class Program extends GetterSetter {
* @private
*/
fatalError(errObj) {
this.logger().error("\n" + errObj.message);
if(this.verbose) {
this.logger().error("\n" + errObj.stack);
} else {
this.logger().error("\n" + errObj.message);
}
process.exit(2);
}

Expand Down Expand Up @@ -158,6 +162,7 @@ class Program extends GetterSetter {
// verbose mode
} else if (options.v || options.verbose) {
this._changeLogLevel('debug');
this.verbose = true;
}

let validated;
Expand Down
98 changes: 91 additions & 7 deletions tests/fatal-error.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
"use strict";

/* global Program, logger, should, sinon */
/* global Program, logger, should, sinon, makeArgv */

const program = new Program();
let program;

program.
logger(logger)
.version('1.0.0');
describe("program.fatalError()", () => {

describe("program.fataError()", () => {
beforeEach(function () {
program = new Program();

program
.logger(logger)
.version('1.0.0');
});

it(`should call logger.error() and exit(2)`, () => {
const error = sinon.stub(logger, 'error').withArgs("\nfoo");
Expand All @@ -20,7 +24,87 @@ describe("program.fataError()", () => {
should(exit.callCount).eql(1);
});

after(function () {
it(`should call logger.error() and exit(2) - verbose`, () => {
const error = sinon.stub(logger, 'error').withArgs(sinon.match('Error: foo\n at '));
const exit = sinon.stub(process, 'exit').withArgs(2);

program
.command('foo', 'Fooooo')
.action(() => { throw new Error('foo'); });

should(program.parse.bind(program, makeArgv(['foo', '-v']))).throw();

should(error.callCount).eql(1);
should(exit.callCount).eql(1);
});

it(`should call logger.error() and exit(2) - normal`, () => {
const error = sinon.stub(logger, 'error').withArgs("\nfoo");
const exit = sinon.stub(process, 'exit').withArgs(2);

program
.command('foo', 'Fooooo')
.action(() => { throw new Error('foo'); });

should(program.parse.bind(program, makeArgv(['foo']))).throw();

should(error.callCount).eql(1);
should(exit.callCount).eql(1);
});

it(`should call logger.error() and exit(2) - async`, (done) => {
const error = sinon.stub(logger, 'error').withArgs("\nfoo");
const exit = sinon.stub(process, 'exit').withArgs(2);

program
.command('foo', 'Fooooo')
.action(() => {
return new Promise((resolve, reject) => {
reject(new Error('foo'));
});
});

program.parse(makeArgv(['foo'])).catch(() => {
should(error.callCount).eql(1);
should(exit.callCount).eql(1);
done();
});
});

it(`should call logger.error() and exit(2) - throw non-exception sync`, () => {
const error = sinon.stub(logger, 'error').withArgs("\nfoo");
const exit = sinon.stub(process, 'exit').withArgs(2);

program
.command('foo', 'Fooooo')
.action(() => { throw 'foo'; });

should(program.parse.bind(program, makeArgv(['foo']))).throw();

should(error.callCount).eql(1);
should(exit.callCount).eql(1);
});

it(`should call logger.error() and exit(2) - throw non-exception async`, (done) => {
const error = sinon.stub(logger, 'error').withArgs("\nfoo");
const exit = sinon.stub(process, 'exit').withArgs(2);

program
.command('foo', 'Fooooo')
.action(() => {
return new Promise((resolve, reject) => {
reject('foo');
});
});

program.parse(makeArgv(['foo'])).catch(() => {
should(error.callCount).eql(1);
should(exit.callCount).eql(1);
done();
});
});

afterEach(function () {
logger.error.restore();
process.exit.restore();
})
Expand Down

0 comments on commit 5d2f935

Please sign in to comment.