Skip to content

Commit

Permalink
XUnit formatter added. Based on VowsJS' reporter.
Browse files Browse the repository at this point in the history
  • Loading branch information
gtramontina committed Jun 23, 2011
1 parent e8769a2 commit f0188fe
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 3 deletions.
95 changes: 95 additions & 0 deletions lib/jessie/formatters/xunit.js
@@ -0,0 +1,95 @@
var sys = require('sys');

exports.formatter = {
specStart: function(spec) {
spec.duration = Number(new Date);
},

reportSpecResults: function(spec) {
spec.duration = (Number(new Date) - spec.duration);
},

finish: function(result, runner) {
sys.puts(tag('testsuite', {
name: 'Jasmine Tests',
tests: result.total,
failures: result.failed,
skip: result.pending,
timestamp: (new Date).toUTCString(),
time: result.duration / 1000
}, false));
runner.suites().forEach(handleSuite);
sys.puts(end('testsuite'));
}
};

function handleSuite(suite) {
suite.specs().forEach(handleSpec);
}

function handleSpec(spec) {
var r = extractResult(spec);
var attributes = {
classname: spec.suite.getFullName(),
name: spec.description,
time: spec.duration / 1000
};

var reason = r.pending? pendingSpecTag(spec) : r.fail? failedSpecTag(spec) : '';
var specTag = reason?
tag('testcase', attributes, false, reason) :
tag('testcase', attributes, true)

sys.puts(specTag);
}

function pendingSpecTag(spec) {
return tag('skipped', { message: encode(spec.results().getItems()[0].trace.message) }, true);
}

function failedSpecTag(spec) {
var reason = spec.results().getItems()[0];
var attributes = { message: encode(reason.message) };
reason.trace.type && (attributes.type = reason.trace.type)
return tag('failure', attributes, false, cdata(reason.trace.stack));
}

function extractResult(spec) {
var pending = false
spec.results().getItems().forEach(function(item) {
pending = item.pending || false;
});
return { pending: pending, fail: spec.results().failedCount > 0 };
}

function encode(value) {
return !value ? value :
String(value)
.replace(/&/g, '&')
.replace(/>/g, '>')
.replace(/</g, '&lt;')
.replace(/"/g, '&quot;')
.replace(/\u001b\[\d{1,2}m/g, '');
}

function tag(name, attribs, single, content) {
var tag;
var end = single ? ' />' : '>'
var strAttr = [];
for (var attr in attribs) {
attribs.hasOwnProperty(attr) && strAttr.push(attr + '="' + encode(attribs[attr]) + '"');
}

tag = '<' + name + (strAttr.length? ' ' + strAttr.join(' ') : '' ) + end
tag = content? (tag + content + '</' + name + end) : tag
return tag;
}

function end(name) {
return '</' + name + '>';
}

function cdata(data) {
return '<![CDATA[' + encode(data) + ']]>';
}

7 changes: 4 additions & 3 deletions spec/jessie/finder_spec.js
Expand Up @@ -3,7 +3,7 @@ describe('jessie.finder', function() {
var finder = new (require('jessie/finder').finder)()

it("should find files if only dir is specified", function() {
finder.find(['spec']).length.should_be(12)
finder.find(['spec']).length.should_be(13)
})

it("should find coffee script files", function() {
Expand All @@ -23,15 +23,16 @@ describe('jessie.finder', function() {
})

it('leaves non-relative paths alone', function() {
finder.find([path.resolve('spec')]).length.should_be(12)
finder.find([path.resolve('spec')]).length.should_be(13)
});

it("should find files if only files are specified", function() {
finder.find(['spec/jessie/finder_spec.js', 'spec/jessie/sugar_spec.js']).length.should_be(2)
})

it("should find files if both dirs and files are specified", function() {
finder.find(['spec/jessie/formatters', 'spec/jessie/finder_spec.js']).length.should_be(3)
finder.find(['spec/jessie/formatters', 'spec/jessie/finder_spec.js']).length.should_be(4)
})

})

103 changes: 103 additions & 0 deletions spec/jessie/formatters/xunit_spec.js
@@ -0,0 +1,103 @@
function outputAsArray(output) {
var array = output.split('\n');
return array.splice(0, array.length-1);
}

/* Remove the surrounding testsuite tag */
function specsAsArray(output) {
var array = outputAsArray(output);
return array.splice(1, array.length-1);
}

describe('XUnit formatter', function() {
var formatter = new (require('jessie/reporter')).reporter('xunit').formatter

var knownResults;
var emptyRunner;
var runnerWithOneSpec;

beforeEach(function() {
knownResults = { total: 2, failed: 1, pending: 1, duration: 2500 };
emptyRunner = { suites: function() { return []; } };
var suiteStub = { getFullName: function() { return 'SuiteName'; } };
var items = [ { message: 'ReasonMessage' , trace: {
type: 'ReasonType',
message: 'TraceReasonMessage',
stack: 'FailureStack'
}}];
var results = { getItems: function() { return items; } }
var specStub = { description: 'SpecName', suite: suiteStub, duration: 5000,
results: function() { return results; }
};
suiteStub.specs = function() { return [ specStub ]; };
runnerWithOneSpec = { suites: function() { return [ suiteStub ]; } };
});

it('should record the start time of a spec', function() {
var emptySpec = {};
formatter.specStart(emptySpec);
expect(emptySpec.duration != undefined).toEqual(true);
expect(emptySpec.duration > 0).toEqual(true);
});

it('should add an open testsuite tag with the summary in the first position of the output buffer', function() {
capture = require('helpers/stdout').capture(function() {
formatter.finish(knownResults, emptyRunner);
});
var tag = outputAsArray(capture.output())[0];
tag.should_match('<testsuite');
tag.should_match('name="Jasmine Tests"');
tag.should_match('tests="2"');
tag.should_match('failures="1"');
tag.should_match('skip="1"');
tag.should_match('time="2.5"');
tag.should_match('timestamp="');
tag.should_match('>');
});

it('should have the last tag as a close testsuite tag', function() {
capture = require('helpers/stdout').capture(function() {
formatter.finish(knownResults, emptyRunner);
});
var output = outputAsArray(capture.output());
var tag = output[output.length - 1];
tag.should_be('</testsuite>');
});

it('should add an open tastcase tag with the summary to the output buffer for successful specs', function() {
capture = require('helpers/stdout').capture(function() {
formatter.finish(knownResults, runnerWithOneSpec);
});
var tag = specsAsArray(capture.output())[0];
tag.should_match('<testcase');
tag.should_match('classname="SuiteName"');
tag.should_match('name="SpecName"');
tag.should_match('time="5"');
tag.should_match('/>');
});

it('should add the reasons of a pended spec to the output buffer', function() {
runnerWithOneSpec.suites()[0].specs()[0].results().getItems()[0].pending = true;
capture = require('helpers/stdout').capture(function() {
formatter.finish(knownResults, runnerWithOneSpec);
});
var tag = specsAsArray(capture.output())[0];
tag.should_match('<skipped');
tag.should_match('message="TraceReasonMessage"');
tag.should_match('/>');
});

it('should add the reasons of a failed spec to the output buffer', function() {
runnerWithOneSpec.suites()[0].specs()[0].results().failedCount = 1;
capture = require('helpers/stdout').capture(function() {
formatter.finish(knownResults, runnerWithOneSpec);
});
var tag = specsAsArray(capture.output())[0];
tag.should_match('<failure');
tag.should_match('message="ReasonMessage"');
tag.should_match('type="ReasonType"');
tag.should_match(/<!\[CDATA\[FailureStack\]\]>/);
tag.should_match('</failure>');
});
});

0 comments on commit f0188fe

Please sign in to comment.