Skip to content

Commit

Permalink
added allowUncaught option (mochajs#553)
Browse files Browse the repository at this point in the history
allows unhandled exceptions to propagate in the browser
added tests for allowUncaught option
global error handler prints to dom with allowUncaught
  • Loading branch information
amsul committed Apr 19, 2015
1 parent 7be4891 commit c627a3d
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 14 deletions.
13 changes: 13 additions & 0 deletions lib/mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,18 @@ Mocha.prototype.noHighlighting = function() {
return this;
};

/**
* Enable uncaught errors to propagate (in browser).
*
* @return {Mocha}
* @api public
*/

Mocha.prototype.allowUncaught = function(){
this.options.allowUncaught = true;
return this;
};

/**
* Delay root suite execution.
* @returns {Mocha}
Expand Down Expand Up @@ -428,6 +440,7 @@ Mocha.prototype.run = function(fn){
runner.ignoreLeaks = false !== options.ignoreLeaks;
runner.fullStackTrace = options.fullStackTrace;
runner.asyncOnly = options.asyncOnly;
runner.allowUncaught = options.allowUncaught;
if (options.grep) runner.grep(options.grep, options.invert);
if (options.globals) runner.globals(options.globals);
if (options.growl) this._growl(runner, reporter);
Expand Down
36 changes: 24 additions & 12 deletions lib/runnable.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,24 +229,22 @@ Runnable.prototype.run = function(fn){
if (this.async) {
this.resetTimeout();

try {
this.fn.call(ctx, function(err){
if (err instanceof Error || toString.call(err) === "[object Error]") return done(err);
if (null != err) {
if (Object.prototype.toString.call(err) === '[object Object]') {
return done(new Error('done() invoked with non-Error: ' + JSON.stringify(err)));
} else {
return done(new Error('done() invoked with non-Error: ' + err));
}
}
done();
});
if (this.allowUncaught) {
callFnAsync(this.fn);
} else try {
callFnAsync(this.fn);
} catch (err) {
done(utils.getError(err));
}
return;
}

if (this.allowUncaught) {
callFn(this.fn);
done();
return;
}

// sync or promise-returning
try {
if (this.pending) {
Expand Down Expand Up @@ -277,4 +275,18 @@ Runnable.prototype.run = function(fn){
done();
}
}

function callFnAsync(fn) {
fn.call(ctx, function(err){
if (err instanceof Error || toString.call(err) === "[object Error]") return done(err);
if (null != err) {
if (Object.prototype.toString.call(err) === '[object Object]') {
return done(new Error('done() invoked with non-Error: ' + JSON.stringify(err)));
} else {
return done(new Error('done() invoked with non-Error: ' + err));
}
}
done();
});
}
};
5 changes: 4 additions & 1 deletion lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,10 @@ Runner.prototype.runTest = function(fn){

if (this.asyncOnly) test.asyncOnly = true;

try {
if (this.allowUncaught) {
test.allowUncaught = true;
test.run(fn);
} else try {
test.on('error', function(err){
self.fail(test, err);
});
Expand Down
2 changes: 1 addition & 1 deletion support/tail.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ process.on = function(e, fn){
if ('uncaughtException' == e) {
global.onerror = function(err, url, line){
fn(new Error(err + ' (' + url + ':' + line + ')'));
return true;
return !mocha.allowUncaught;
};
uncaughtExceptionHandlers.push(fn);
}
Expand Down
29 changes: 29 additions & 0 deletions test/runnable.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,20 @@ describe('Runnable(title, fn)', function(){
})
})
})

describe('when an exception is thrown and is allowed to remain uncaught', function(){
it('throws an error when it is allowed', function(done) {
var test = new Runnable('foo', function(){
throw new Error('fail');
});
test.allowUncaught = true;
function fail() {
test.run(function(err) {});
}
fail.should.throw('fail');
done();
})
})
})

describe('when timeouts are disabled', function() {
Expand Down Expand Up @@ -239,6 +253,21 @@ describe('Runnable(title, fn)', function(){
});
})

describe('when an exception is thrown and is allowed to remain uncaught', function(){
it('throws an error when it is allowed', function(done) {
var test = new Runnable('foo', function(done){
throw new Error('fail');
process.nextTick(done);
});
test.allowUncaught = true;
function fail() {
test.run(function(err) {});
}
fail.should.throw('fail');
done();
})
})

describe('when an error is passed', function(){
it('should invoke the callback', function(done){
var calls = 0;
Expand Down
15 changes: 15 additions & 0 deletions test/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,21 @@ describe('Runner', function(){
})
});

describe('allowUncaught', function() {
it('should allow unhandled errors to propagate through', function(done) {
var newRunner = new Runner(suite);
newRunner.allowUncaught = true;
newRunner.test = new Test('failing test', function() {
throw new Error('allow unhandled errors');
});
function fail() {
newRunner.runTest();
}
fail.should.throw('allow unhandled errors');
done();
});
});

describe('stackTrace', function() {
var stack = [ 'AssertionError: foo bar'
, 'at EventEmitter.<anonymous> (/usr/local/dev/test.js:16:12)'
Expand Down

0 comments on commit c627a3d

Please sign in to comment.