Skip to content

Commit

Permalink
feat(runner/stack-trace): solve issue #545 + test
Browse files Browse the repository at this point in the history
  • Loading branch information
a8m committed Mar 7, 2015
1 parent 9123d7a commit 558191d
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 12 deletions.
5 changes: 5 additions & 0 deletions bin/_mocha
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ program
.option('-u, --ui <name>', 'specify user-interface (bdd|tdd|exports)', 'bdd')
.option('-w, --watch', 'watch files for changes')
.option('--check-leaks', 'check for global variable leaks')
.option('--full-trace', 'display the full stack trace')
.option('--compilers <ext>:<module>,...', 'use the given module(s) to compile files', list, [])
.option('--debug-brk', "enable node's debugger breaking on the first line")
.option('--globals <names>', 'allow the given comma-delimited global [names]', list, [])
Expand Down Expand Up @@ -274,6 +275,10 @@ if (program.invert) mocha.invert();

if (program.checkLeaks) mocha.checkLeaks();

// --stack-trace

if(program.fullTrace) mocha.fullTrace();

// --growl

if (program.growl) mocha.growl();
Expand Down
14 changes: 14 additions & 0 deletions lib/mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ function image(name) {
* - `bail` bail on the first test failure
* - `slow` milliseconds to wait before considering a test slow
* - `ignoreLeaks` ignore global leaks
* - `fullTrace` display the full stack-trace on failing
* - `grep` string or regexp to filter tests with
*
* @param {Object} options
Expand Down Expand Up @@ -265,6 +266,18 @@ Mocha.prototype.checkLeaks = function(){
return this;
};

/**
* Display long stack-trace on failing
*
* @return {Mocha}
* @api public
*/

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

/**
* Enable growl support.
*
Expand Down Expand Up @@ -408,6 +421,7 @@ Mocha.prototype.run = function(fn){
var runner = new exports.Runner(suite, options.delay);
var reporter = new this._reporter(runner, options);
runner.ignoreLeaks = false !== options.ignoreLeaks;
runner.fullStackTrace = options.fullStackTrace;
runner.asyncOnly = options.asyncOnly;
if (options.grep) runner.grep(options.grep, options.invert);
if (options.globals) runner.globals(options.globals);
Expand Down
13 changes: 8 additions & 5 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ var EventEmitter = require('events').EventEmitter
, filter = utils.filter
, keys = utils.keys
, type = utils.type
, stringify = utils.stringify;
, stringify = utils.stringify
, stackFilter = utils.stackTraceFilter();

/**
* Non-enumerable globals.
Expand Down Expand Up @@ -197,16 +198,18 @@ Runner.prototype.checkGlobals = function(test){
* @api private
*/

Runner.prototype.fail = function(test, err){
Runner.prototype.fail = function(test, err) {
++this.failures;
test.state = 'failed';

if ('string' == typeof err) {
err = new Error('the string "' + err + '" was thrown, throw an Error :)');
} else if (!(err instanceof Error)) {
if (!(err instanceof Error)) {
err = new Error('the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)');
}

err.stack = this.fullStackTrace
? err.stack
: stackFilter(err.stack);

this.emit('fail', test, err);
};

Expand Down
65 changes: 65 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -629,3 +629,68 @@ exports.getError = function(err) {
return err || exports.undefinedError();
};


/**
* @summary
* This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`)
* @description
* When invoking this function you get a filter function that get the Error.stack as an input,
* and return a prettify output.
* (i.e: strip Mocha, node_modules, bower and componentJS from stack trace).
* @returns {Function}
*/

exports.stackTraceFilter = function() {
var slash = '/'
, is = typeof document === 'undefined'
? { node: true }
: { browser: true }
, cwd = is.node
? process.cwd() + slash

This comment has been minimized.

Copy link
@benjamn

benjamn Jun 13, 2015

I'm seeing this function called at a time when this process refers to the variable

mocha/mocha.js

Line 6426 in f08b789

var process = {};
, not the global process object, so there is no .cwd method… any idea what to do about that?

This comment has been minimized.

Copy link
@benjamn

benjamn Jun 13, 2015

Update: I was accidentally running mocha.js as if it contained tests.

This comment has been minimized.

Copy link
@jbnicolai

jbnicolai Jun 13, 2015

Something like process.cwd = function() { return ''; };?

This comment has been minimized.

Copy link
@benjamn

benjamn Jun 13, 2015

Yeah, I think that would have worked, though in my particular case, stackTraceFilter was called before the var process = {} line, so process was still undefined (because of var hoisting). Anyway, now that I'm not running mocha test/mocha.js, everything works just fine!

: location.href.replace(/\/[^\/]*$/, '/');

function isNodeModule (line) {
return (~line.indexOf('node_modules'));
}

function isMochaInternal (line) {
return (~line.indexOf('node_modules' + slash + 'mocha')) ||
(~line.indexOf('components' + slash + 'mochajs')) ||
(~line.indexOf('components' + slash + 'mocha'));
}

// node_modules, bower, componentJS
function isBrowserModule(line) {
return (~line.indexOf('node_modules')) ||
(~line.indexOf('components'));
}

function isNodeInternal (line) {
return (~line.indexOf('(timers.js:')) ||
(~line.indexOf('(events.js:')) ||
(~line.indexOf('(node.js:')) ||
(~line.indexOf('(module.js:')) ||
(~line.indexOf('GeneratorFunctionPrototype.next (native)')) ||
false
}

return function(stack) {
stack = stack.split('\n');

stack = stack.reduce(function (list, line) {
if (is.node && (isNodeModule(line) ||
isMochaInternal(line) ||
isNodeInternal(line)))
return list;

if (is.browser && (isBrowserModule(line)))
return list;

// Clean up cwd(absolute)
list.push(line.replace(cwd, ''));
return list;
}, []);

return stack.join('\n');
}
};
91 changes: 86 additions & 5 deletions mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -1470,6 +1470,7 @@ function image(name) {
* - `bail` bail on the first test failure
* - `slow` milliseconds to wait before considering a test slow
* - `ignoreLeaks` ignore global leaks
* - `fullTrace` display the full stack-trace on failing
* - `grep` string or regexp to filter tests with
*
* @param {Object} options
Expand Down Expand Up @@ -1669,6 +1670,18 @@ Mocha.prototype.checkLeaks = function(){
return this;
};

/**
* Display long stack-trace on failing
*
* @return {Mocha}
* @api public
*/

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

/**
* Enable growl support.
*
Expand Down Expand Up @@ -1812,6 +1825,7 @@ Mocha.prototype.run = function(fn){
var runner = new exports.Runner(suite, options.delay);
var reporter = new this._reporter(runner, options);
runner.ignoreLeaks = false !== options.ignoreLeaks;
runner.fullStackTrace = options.fullStackTrace;
runner.asyncOnly = options.asyncOnly;
if (options.grep) runner.grep(options.grep, options.invert);
if (options.globals) runner.globals(options.globals);
Expand Down Expand Up @@ -4560,7 +4574,8 @@ var EventEmitter = require('browser/events').EventEmitter
, filter = utils.filter
, keys = utils.keys
, type = utils.type
, stringify = utils.stringify;
, stringify = utils.stringify
, stackFilter = utils.stackTraceFilter();

/**
* Non-enumerable globals.
Expand Down Expand Up @@ -4751,16 +4766,18 @@ Runner.prototype.checkGlobals = function(test){
* @api private
*/

Runner.prototype.fail = function(test, err){
Runner.prototype.fail = function(test, err) {
++this.failures;
test.state = 'failed';

if ('string' == typeof err) {
err = new Error('the string "' + err + '" was thrown, throw an Error :)');
} else if (!(err instanceof Error)) {
if (!(err instanceof Error)) {
err = new Error('the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)');
}

err.stack = this.fullStackTrace
? err.stack
: stackFilter(err.stack);

this.emit('fail', test, err);
};

Expand Down Expand Up @@ -6304,6 +6321,70 @@ exports.getError = function(err) {
};


/**
* @summary
* This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`)
* @description
* When invoking this function you get a filter function that get the Error.stack as an input,
* and return a prettify output.
* (i.e: strip Mocha, node_modules, bower and componentJS from stack trace).
* @returns {Function}
*/

exports.stackTraceFilter = function() {
var slash = '/'
, is = typeof document === 'undefined'
? { node: true }
: { browser: true }
, cwd = is.node
? process.cwd() + slash
: location.href.replace(/\/[^\/]*$/, '/');

function isNodeModule (line) {
return (~line.indexOf('node_modules'));
}

function isMochaInternal (line) {
return (~line.indexOf('node_modules' + slash + 'mocha')) ||
(~line.indexOf('components' + slash + 'mochajs')) ||
(~line.indexOf('components' + slash + 'mocha'));
}

// node_modules, bower, componentJS
function isBrowserModule(line) {
return (~line.indexOf('node_modules')) ||
(~line.indexOf('components'));
}

function isNodeInternal (line) {
return (~line.indexOf('(timers.js:')) ||
(~line.indexOf('(events.js:')) ||
(~line.indexOf('(node.js:')) ||
(~line.indexOf('(module.js:')) ||
(~line.indexOf('GeneratorFunctionPrototype.next (native)')) ||
false
}

return function(stack) {
stack = stack.split('\n');

stack = stack.reduce(function (list, line) {
if (is.node && (isNodeModule(line) ||
isMochaInternal(line) ||
isNodeInternal(line)))
return list;

if (is.browser && (isBrowserModule(line)))
return list;

// Clean up cwd(absolute)
list.push(line.replace(cwd, ''));
return list;
}, []);

return stack.join('\n');
}
};
}); // module: utils.js
// The global object is "self" in Web Workers.
var global = (function() { return this; })();
Expand Down
24 changes: 24 additions & 0 deletions test/browser/stack-trace.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<html>
<head>
<title>Mocha</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../../mocha.css" />
<script src="../../mocha.js"></script>
<script>mocha.setup('bdd')</script>
<script>
function assert(expr, err) {
if (!expr) throw err;
}
</script>
<script src="stack-trace.js"></script>
<script>
onload = function() {
mocha.run();
};
</script>
</head>
<body>
<div id="mocha"></div>
</body>
</html>
20 changes: 20 additions & 0 deletions test/browser/stack-trace.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';
describe('Stack trace', function() {
it('should prettify the stack-trace', function() {
var err = new Error();
// We do this fake stack-trace because we under development,
// and our root isn't `node_modules`, `bower` or `components`
err.stack = ['Error: failed'
, 'at assert (stack-trace.html:11:30)'
, 'at Context.<anonymous> (stack-trace.js:5:5)'
, 'at callFn (http://localhost:63342/node_modules/mocha.js:4546:21)'
, 'at Test.require.register.Runnable.run (http://localhost:63342/node_modules/mocha.js:4539:7)'
, 'at Runner.require.register.Runner.runTest (http://localhost:63342/node_modules/mocha.js:4958:10)'
, 'at http://localhost:63342/bower_components/mocha.js:5041:12'
, 'at next (http://localhost:63342/bower_components/mocha.js:4883:14)'
, 'at http://localhost:63342/bower_components/mocha.js:4893:7'
, 'at next (http://localhost:63342/bower_components/mocha.js:4828:23)'
, 'at http://localhost:63342/bower_components/mocha.js:4860:5'].join('\n');
assert(false, err);
});
});
50 changes: 48 additions & 2 deletions test/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,5 +290,51 @@ describe('Runner', function(){
runner.failHook(hook, err);
done();
})
})
})
});

describe('stackTrace', function() {
var stack = [ 'AssertionError: foo bar'
, 'at EventEmitter.<anonymous> (/usr/local/dev/test.js:16:12)'
, 'at Context.<anonymous> (/usr/local/dev/test.js:19:5)'
, 'Test.Runnable.run (/usr/local/lib/node_modules/mocha/lib/runnable.js:244:7)'
, 'Runner.runTest (/usr/local/lib/node_modules/mocha/lib/runner.js:374:10)'
, '/usr/local/lib/node_modules/mocha/lib/runner.js:452:12'
, 'next (/usr/local/lib/node_modules/mocha/lib/runner.js:299:14)'
, '/usr/local/lib/node_modules/mocha/lib/runner.js:309:7'
, 'next (/usr/local/lib/node_modules/mocha/lib/runner.js:248:23)'
, 'Immediate._onImmediate (/usr/local/lib/node_modules/mocha/lib/runner.js:276:5)'
, 'at processImmediate [as _immediateCallback] (timers.js:321:17)'];

describe('shortStackTrace', function() {
it('should prettify the stack-trace', function(done) {
var hook = {},
err = new Error();
// Fake stack-trace
err.stack = stack.join('\n');

runner.on('fail', function(hook, err){
err.stack.should.equal(stack.slice(0,3).join('\n'));
done();
});
runner.failHook(hook, err);
});
});

describe('longStackTrace', function() {
it('should display the full stack-trace', function(done) {
var hook = {},
err = new Error();
// Fake stack-trace
err.stack = stack.join('\n');
// Add --stack-trace option
runner.fullStackTrace = true;

runner.on('fail', function(hook, err){
err.stack.should.equal(stack.join('\n'));
done();
});
runner.failHook(hook, err);
});
});
});
});
Loading

0 comments on commit 558191d

Please sign in to comment.