Skip to content

Instantly share code, notes, and snippets.

@keis
Created December 4, 2013 11:47
Show Gist options
  • Save keis/7786301 to your computer and use it in GitHub Desktop.
Save keis/7786301 to your computer and use it in GitHub Desktop.
extend mocha dot reporter with log4js capture
var log4js = require('log4js'),
Base = require('mocha/lib/reporters/base'),
Dot = require('mocha/lib/reporters/dot'),
diff = require('diff'),
ms = require('mocha/lib/ms'),
color = Base.color;
function LogReport(runner) {
var captured = [],
layout = log4js.layouts.colouredLayout;
Dot.call(this, runner);
runner.on('start', function () {
log4js.clearAppenders();
log4js.addAppender(function (logEvent) {
captured.push(layout(logEvent));
});
});
runner.on('test', function () {
captured = [];
});
runner.on('fail', function (test) {
if (captured.length) {
test.capturedLog = captured;
}
});
}
/**
* Check that a / b have the same type.
*
* @param {Object} a
* @param {Object} b
* @return {Boolean}
* @api private
*/
function sameType(a, b) {
a = Object.prototype.toString.call(a);
b = Object.prototype.toString.call(b);
return a == b;
}
/**
* Returns a string with all invisible characters in plain text
*
* @param {String} line
* @return {String}
* @api private
*/
function escapeInvisibles(line) {
return line.replace(/\t/g, '<tab>')
.replace(/\r/g, '<CR>')
.replace(/\n/g, '<LF>\n');
}
/**
* Color lines for `str`, using the color `name`.
*
* @param {String} name
* @param {String} str
* @return {String}
* @api private
*/
function colorLines(name, str) {
return str.split('\n').map(function(str){
return color(name, str);
}).join('\n');
}
/**
* Returns an inline diff between 2 strings with coloured ANSI output
*
* @param {Error} Error with actual/expected
* @return {String} Diff
* @api private
*/
function inlineDiff(err, escape) {
var msg = errorDiff(err, 'WordsWithSpace', escape);
// linenos
var lines = msg.split('\n');
if (lines.length > 4) {
var width = String(lines.length).length;
msg = lines.map(function(str, i){
return pad(++i, width) + ' |' + ' ' + str;
}).join('\n');
}
// legend
msg = '\n'
+ color('diff removed', 'actual')
+ ' '
+ color('diff added', 'expected')
+ '\n\n'
+ msg
+ '\n';
// indent
msg = msg.replace(/^/gm, ' ');
return msg;
}
/**
* Returns a unified diff between 2 strings
*
* @param {Error} Error with actual/expected
* @return {String} Diff
* @api private
*/
function unifiedDiff(err, escape) {
var indent = ' ';
function cleanUp(line) {
if (escape) {
line = escapeInvisibles(line);
}
if (line[0] === '+') return indent + colorLines('diff added', line);
if (line[0] === '-') return indent + colorLines('diff removed', line);
if (line.match(/\@\@/)) return null;
if (line.match(/\\ No newline/)) return null;
else return indent + line;
}
function notBlank(line) {
return line != null;
}
msg = diff.createPatch('string', err.actual, err.expected);
var lines = msg.split('\n').splice(4);
return '\n '
+ colorLines('diff added', '+ expected') + ' '
+ colorLines('diff removed', '- actual')
+ '\n\n'
+ lines.map(cleanUp).filter(notBlank).join('\n');
}
/**
* Outut the given `failures` as a list.
*
* @param {Array} failures
* @api public
*/
function list(failures) {
console.error();
failures.forEach(function(test, i){
// format
var fmt = color('error title', ' %s) %s:\n')
+ color('error message', ' %s')
+ color('error stack', '\n%s\n');
// msg
var err = test.err
, message = err.message || ''
, stack = err.stack || message
, index = stack.indexOf(message) + message.length
, msg = stack.slice(0, index)
, actual = err.actual
, expected = err.expected
, escape = true;
// uncaught
if (err.uncaught) {
msg = 'Uncaught ' + msg;
}
// explicitly show diff
if (err.showDiff && sameType(actual, expected)) {
escape = false;
err.actual = actual = stringify(actual);
err.expected = expected = stringify(expected);
}
// actual / expected diff
if ('string' == typeof actual && 'string' == typeof expected) {
fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n');
var match = message.match(/^([^:]+): expected/);
msg = match ? '\n ' + color('error message', match[1]) : '';
if (exports.inlineDiffs) {
msg += inlineDiff(err, escape);
} else {
msg += unifiedDiff(err, escape);
}
}
if (test.capturedLog) {
msg += '\n Captured log messages:\n';
test.capturedLog.forEach(function (log) {
msg += ' ' + log;
});
}
// indent stack trace without msg
stack = stack.slice(index ? index + 1 : index)
.replace(/^/gm, ' ');
console.error(fmt, (i + 1), test.fullTitle(), msg, stack);
});
}
LogReport.prototype = Object.create(Dot.prototype);
LogReport.prototype.epilogue = function () {
var stats = this.stats;
var tests;
var fmt;
console.log();
// passes
fmt = color('bright pass', ' ')
+ color('green', ' %d passing')
+ color('light', ' (%s)');
console.log(fmt,
stats.passes || 0,
ms(stats.duration));
// pending
if (stats.pending) {
fmt = color('pending', ' ')
+ color('pending', ' %d pending');
console.log(fmt, stats.pending);
}
// failures
if (stats.failures) {
fmt = color('fail', ' %d failing');
console.error(fmt,
stats.failures);
list(this.failures);
console.error();
}
};
module.exports = LogReport;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment