Skip to content

Commit

Permalink
added allowUncaught option (mochajs#553)
Browse files Browse the repository at this point in the history
added tests for allowUncaught option

error handler prints to dom with allowUncaught
  • Loading branch information
amsul committed Apr 18, 2015
1 parent 5bc6500 commit cd132a9
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 32 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 = global.mocha.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
44 changes: 28 additions & 16 deletions lib/runnable.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,24 +229,26 @@ 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.asyncOnly) {
return done(new Error('--async-only option in use without declaring `done()`'));
}

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

// sync or promise-returning
try {
if (this.pending) {
Expand All @@ -270,11 +272,21 @@ Runnable.prototype.run = function(fn){
done(reason || new Error('Promise rejected with no or falsy reason'))
});
} else {
if (self.asyncOnly) {
return done(new Error('--async-only option in use without declaring `done()` or returning a promise'));
}

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
56 changes: 42 additions & 14 deletions mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -1805,6 +1805,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 = global.mocha.allowUncaught = true;
return this;
};

/**
* Delay root suite execution.
* @returns {Mocha}
Expand Down Expand Up @@ -1832,6 +1844,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 Expand Up @@ -4525,18 +4538,10 @@ 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));
}
Expand All @@ -4547,6 +4552,12 @@ Runnable.prototype.run = function(fn){
return done(new Error('--async-only option in use without declaring `done()`'));
}

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

// sync or promise-returning
try {
if (this.pending) {
Expand All @@ -4573,6 +4584,20 @@ 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();
});
}
};

}); // module: runnable.js
Expand Down Expand Up @@ -4967,7 +4992,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 Expand Up @@ -6456,7 +6484,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
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 cd132a9

Please sign in to comment.