Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #5 from gtramontina/master

An XUnit formatter implementation
  • Loading branch information...
commit 2f4415300cb01d4b2e97adc5c9d1c23f1f3c029a 2 parents 4f1c767 + f0188fe
@marcinbunsch marcinbunsch authored
View
95 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) + ']]>';
+}
+
View
7 spec/jessie/finder_spec.js
@@ -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() {
@@ -23,7 +23,7 @@ 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() {
@@ -31,7 +31,8 @@ describe('jessie.finder', function() {
})
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)
})
})
+
View
65 spec/jessie/formatters/nested_spec.js
@@ -1,10 +1,10 @@
describe('formatters', function() {
var reporter = new (require('jessie/reporter')).reporter('nested')
-
+
describe('nested formatter', function() {
-
+
describe('single spec', function() {
-
+
it("should use the green spec description to render a successful spec", function() {
capture = require('helpers/stdout').capture(function() {
reporter.formatter.spec({fail: false}, {description: 'my spec'})
@@ -23,7 +23,7 @@ describe('formatters', function() {
reporter.formatter.depth = original_depth
})
- it("should use the yello spec description to render a pending spec", function() {
+ it("should use the yellow spec description to render a pending spec", function() {
var original_depth = reporter.formatter.depth
capture = require('helpers/stdout').capture(function() {
reporter.formatter.spec({pending: true}, {description: 'my spec'})
@@ -32,7 +32,7 @@ describe('formatters', function() {
capture.output().should_match(/\033\[33m/) // yellow
reporter.formatter.depth = original_depth
})
-
+
it("should render the spec with appropriate spacing on the left", function() {
var original_depth = reporter.formatter.depth
reporter.formatter.depth = 1
@@ -49,25 +49,25 @@ describe('formatters', function() {
capture.output().should_match(/^ {4}/)
reporter.formatter.depth = original_depth
})
-
+
})
-
+
describe('single suite', function() {
it("should render the suite name", function() {
var original_depth = reporter.formatter.depth
-
+
capture = require('helpers/stdout').capture(function() {
reporter.formatter.suiteStart({description: 'my suite'})
})
capture.output().should_match('my suite')
reporter.formatter.depth = original_depth
-
- })
+
+ })
it("should render the suite name with appropriate spacing on the left", function() {
var original_depth = reporter.formatter.depth
-
+
reporter.formatter.depth = 1
capture = require('helpers/stdout').capture(function() {
reporter.formatter.suiteStart({description: 'my suite'})
@@ -78,7 +78,7 @@ describe('formatters', function() {
reporter.formatter.depth.should_be(2)
reporter.formatter.suite()
reporter.formatter.depth.should_be(1)
-
+
reporter.formatter.depth = 2
capture = require('helpers/stdout').capture(function() {
reporter.formatter.suiteStart({description: 'my suite'})
@@ -91,31 +91,31 @@ describe('formatters', function() {
it("should increase depth when starting a suite", function() {
var original_depth = reporter.formatter.depth
-
+
reporter.formatter.depth = 1
capture = require('helpers/stdout').capture(function() {
reporter.formatter.suiteStart({description: 'my suite'})
})
-
+
reporter.formatter.depth.should_be(2)
reporter.formatter.depth = original_depth
-
+
})
-
+
it("should decrease depth when ending a suite", function() {
var original_depth = reporter.formatter.depth
-
+
reporter.formatter.depth = 2
reporter.formatter.suite()
reporter.formatter.depth.should_be(1)
-
+
reporter.formatter.depth = original_depth
-
+
})
-
+
})
-
+
describe('summary', function() {
var result = {duration: 15, failed: 1, total: 2}
result.failures= [{
@@ -123,7 +123,7 @@ describe('formatters', function() {
message: 'Expected A to be B',
stacktrace: ['Expected A to be B', 'file.js:23:2', 'another_file.js:30:11']
}]
-
+
it("should properly render a summary", function() {
capture = require('helpers/stdout').capture(function() {
reporter.formatter.finish(result)
@@ -135,9 +135,9 @@ describe('formatters', function() {
capture.output().should_match('Expected A to be B')
capture.output().should_match('file.js:23:2')
capture.output().should_match('another_file.js:30:11')
-
+
})
-
+
it("should properly render a summary with pending and failing specs", function() {
var result = {duration: 15, failed: 1, total: 3, pending: 1}
result.failures= [{
@@ -145,7 +145,7 @@ describe('formatters', function() {
message: 'Expected A to be B',
stacktrace: ['Expected A to be B', 'file.js:23:2', 'another_file.js:30:11']
}]
-
+
capture = require('helpers/stdout').capture(function() {
reporter.formatter.finish(result)
})
@@ -156,7 +156,7 @@ describe('formatters', function() {
capture.output().should_match('it should print errors')
capture.output().should_match('Expected A to be B')
capture.output().should_match('file.js:23:2')
- capture.output().should_match('another_file.js:30:11')
+ capture.output().should_match('another_file.js:30:11')
})
it("should properly render a summary with pending and failing specs", function() {
@@ -166,7 +166,7 @@ describe('formatters', function() {
message: 'Expected A to be B',
stacktrace: ['Expected A to be B', 'file.js:23:2', 'another_file.js:30:11']
}]
-
+
capture = require('helpers/stdout').capture(function() {
reporter.formatter.finish(result)
})
@@ -177,11 +177,12 @@ describe('formatters', function() {
capture.output().should_match('it should print errors')
capture.output().should_match('Expected A to be B')
capture.output().should_match('file.js:23:2')
- capture.output().should_match('another_file.js:30:11')
+ capture.output().should_match('another_file.js:30:11')
})
-
-
+
+
})
})
-
-})
+
+})
+
View
33 spec/jessie/formatters/progress_spec.js
@@ -1,10 +1,10 @@
describe('formatters', function() {
var reporter = new (require('jessie/reporter')).reporter('progress')
-
+
describe('progress formatter', function() {
-
+
describe('single spec', function() {
-
+
it("should use a dot to render a successful spec", function() {
capture = require('helpers/stdout').capture(function() {
reporter.formatter.spec({fail: false})
@@ -13,7 +13,7 @@ describe('formatters', function() {
capture.output().should_match(/\033\[32m/) // green
})
- it("should use a start to render a pending spec", function() {
+ it("should use a star to render a pending spec", function() {
capture = require('helpers/stdout').capture(function() {
reporter.formatter.spec({pending: true})
})
@@ -21,16 +21,16 @@ describe('formatters', function() {
capture.output().should_match(/\033\[33m/) // yellow
})
- it("should use a F to render a successful spec", function() {
+ it("should use a F to render a failed spec", function() {
capture = require('helpers/stdout').capture(function() {
reporter.formatter.spec({fail: true})
})
capture.output().should_match('F')
capture.output().should_match(/\033\[31m/) // red
})
-
+
})
-
+
describe('summary', function() {
var result = {duration: 15, failed: 1, total: 2}
result.failures= [{
@@ -38,7 +38,7 @@ describe('formatters', function() {
message: 'Expected A to be B',
stacktrace: ['Expected A to be B', 'file.js:23:2', 'another_file.js:30:11']
}]
-
+
it("should properly render a summary", function() {
capture = require('helpers/stdout').capture(function() {
reporter.formatter.finish(result)
@@ -50,7 +50,7 @@ describe('formatters', function() {
capture.output().should_match('Expected A to be B')
capture.output().should_match('file.js:23:2')
capture.output().should_match('another_file.js:30:11')
-
+
})
it("should properly render a summary with pending and failing specs", function() {
@@ -60,7 +60,7 @@ describe('formatters', function() {
message: 'Expected A to be B',
stacktrace: ['Expected A to be B', 'file.js:23:2', 'another_file.js:30:11']
}]
-
+
capture = require('helpers/stdout').capture(function() {
reporter.formatter.finish(result)
})
@@ -71,7 +71,7 @@ describe('formatters', function() {
capture.output().should_match('it should print errors')
capture.output().should_match('Expected A to be B')
capture.output().should_match('file.js:23:2')
- capture.output().should_match('another_file.js:30:11')
+ capture.output().should_match('another_file.js:30:11')
})
it("should properly render a summary with pending and failing specs", function() {
@@ -81,7 +81,7 @@ describe('formatters', function() {
message: 'Expected A to be B',
stacktrace: ['Expected A to be B', 'file.js:23:2', 'another_file.js:30:11']
}]
-
+
capture = require('helpers/stdout').capture(function() {
reporter.formatter.finish(result)
})
@@ -92,10 +92,11 @@ describe('formatters', function() {
capture.output().should_match('it should print errors')
capture.output().should_match('Expected A to be B')
capture.output().should_match('file.js:23:2')
- capture.output().should_match('another_file.js:30:11')
+ capture.output().should_match('another_file.js:30:11')
})
-
+
})
})
-
-})
+
+})
+
View
103 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>');
+ });
+});
+
Please sign in to comment.
Something went wrong with that request. Please try again.