Permalink
Browse files

Create and test SolanoReporter

Just a slightly tweaked XUnit reporter
  • Loading branch information...
pjmorse committed Feb 9, 2017
1 parent 9dc9ede commit de95e05fc91b05a1b597c793750aada25b2761d3
Showing with 280 additions and 1 deletion.
  1. +1 −0 lib/reporters/index.js
  2. +107 −0 lib/reporters/solano_reporter.js
  3. +1 −1 testem.js
  4. +171 −0 tests/ci/reporter_tests.js
View
@@ -3,6 +3,7 @@
module.exports = {
tap: require('./tap_reporter'),
xunit: require('./xunit_reporter'),
+ solano: require('./solano_reporter'),
dot: require('./dot_reporter'),
teamcity: require('./teamcity_reporter'),
dev: require('./dev')
@@ -0,0 +1,107 @@
+'use strict';
+
+var XmlDom = require('xmldom');
+
+function SolanoReporter(silent, out, config) {
+ this.out = out || process.stdout;
+ this.excludeStackTraces = config.get('xunit_exclude_stack');
+ this.silent = silent;
+ this.stoppedOnError = null;
+ this.id = 1;
+ this.total = 0;
+ this.pass = 0;
+ this.skipped = 0;
+ this.results = [];
+ this.startTime = new Date();
+ this.endTime = null;
+}
+SolanoReporter.prototype = {
+ report: function(prefix, data) {
+ this.results.push({
+ launcher: prefix,
+ result: data
+ });
+ this.display(prefix, data);
+ this.total++;
+ if (data.skipped) {
+ this.skipped++;
+ } else if (data.passed) {
+ this.pass++;
+ }
+ },
+ finish: function() {
+ if (this.silent) {
+ return;
+ }
+ this.endTime = new Date();
+ this.out.write(this.summaryDisplay());
+ this.out.write('\n');
+ },
+ summaryDisplay: function() {
+ var doc = new XmlDom.DOMImplementation().createDocument('', 'testsuite');
+
+ var rootNode = doc.documentElement;
+ rootNode.setAttribute('name', 'Testem Tests');
+ rootNode.setAttribute('tests', this.total);
+ rootNode.setAttribute('skipped', this.skipped);
+ rootNode.setAttribute('failures', this.failures());
+ rootNode.setAttribute('timestamp', new Date());
+ rootNode.setAttribute('time', this.duration());
+
+ for (var i = 0, len = this.results.length; i < len; i++) {
+ var testcaseNode = this.getTestResultNode(doc, this.results[i]);
+ rootNode.appendChild(testcaseNode);
+ }
+ return doc.documentElement.toString();
+ },
+ display: function() {
+ // As the output is XML, the XUnitReporter can only write its results after all
+ // tests have finished.
+ return;
+ },
+ getTestResultNode: function(document, result) {
+ var launcher = result.launcher;
+ result = result.result;
+
+ var resultNode = document.createElement('testcase');
+ resultNode.setAttribute('classname', result.name);
+ resultNode.setAttribute('name', launcher);
+ resultNode.setAttribute('time', this._durationFromMs(result.runDuration));
+
+ var error = result.error;
+ if (error) {
+ var errorNode = document.createElement('error');
+ errorNode.setAttribute('message', error.message);
+ if (error.stack && !this.excludeStackTraces) {
+ var cdata = document.createCDATASection(error.stack);
+ errorNode.appendChild(cdata);
+ }
+ resultNode.appendChild(errorNode);
+ } else if (result.skipped) {
+ var skippedNode = document.createElement('skipped');
+ resultNode.appendChild(skippedNode);
+ } else if (!result.passed) {
+ var failureNode = document.createElement('failure');
+ resultNode.appendChild(failureNode);
+ }
+
+ return resultNode;
+ },
+ failures: function() {
+ return this.total - this.pass - this.skipped;
+ },
+ duration: function() {
+ return this._durationFromMs(this.endTime - this.startTime);
+ },
+ _durationFromMs: function(ms) {
+ if (ms)
+ {
+ return (ms / 1000).toFixed(3);
+ } else
+ {
+ return 0;
+ }
+ }
+};
+
+module.exports = SolanoReporter;
View
@@ -34,7 +34,7 @@ program
.option('-T, --timeout [sec]', 'timeout a browser after [sec] seconds', null)
.option('-P, --parallel [num]', 'number of browsers to run in parallel, defaults to 1', Number)
.option('-b, --bail_on_uncaught_error', 'Bail on any uncaught errors')
- .option('-R, --reporter [reporter]', 'Test reporter to use [tap|dot|xunit|teamcity]')
+ .option('-R, --reporter [reporter]', 'Test reporter to use [tap|dot|xunit|solano|teamcity]')
.action(act(function(env) {
env.__proto__ = program;
progOptions = env;
View
@@ -3,6 +3,7 @@
var TapReporter = require('../../lib/reporters/tap_reporter');
var DotReporter = require('../../lib/reporters/dot_reporter');
var XUnitReporter = require('../../lib/reporters/xunit_reporter');
+var SolanoReporter = require('../../lib/reporters/solano_reporter');
var TeamcityReporter = require('../../lib/reporters/teamcity_reporter');
var Config = require('../../lib/config');
var PassThrough = require('stream').PassThrough;
@@ -367,6 +368,176 @@ describe('test reporters', function() {
});
});
+ describe('solano reporter', function() {
+ var config, stream;
+
+ beforeEach(function() {
+ config = new Config('ci', {
+ xunit_intermediate_output: false
+ });
+ stream = new PassThrough();
+ });
+
+ it('writes out and XML escapes results', function() {
+ var reporter = new SolanoReporter(false, stream, config);
+ reporter.report('phantomjs', {
+ name: 'it does <cool> \"cool\" \'cool\' stuff',
+ passed: true
+ });
+ reporter.finish();
+ var output = stream.read().toString();
+ assert.match(output, /<testsuite name="Testem Tests" tests="1" skipped="0" failures="0" timestamp="(.+)" time="(\d+(\.\d+)?)">/);
+ assert.match(output, /<testcase classname="it does &lt;cool> &quot;cool&quot; \'cool\' stuff" name="phantomjs"/);
+
+ assertXmlIsValid(output);
+ });
+
+ it('does not print intermediate test results when intermediate output is disabled', function() {
+ var reporter = new SolanoReporter(false, stream, config);
+ var displayed = false;
+ var write = process.stdout.write;
+ process.stdout.write = function(string, encoding, fd) {
+ write.apply(process.stdout, [string, encoding, fd]);
+ displayed = true;
+ };
+ reporter.report('phantomjs', {
+ name: 'it does stuff',
+ passed: true,
+ logs: []
+ });
+ assert(!displayed);
+ process.stdout.write = write;
+ });
+
+ it('outputs errors', function() {
+ var reporter = new SolanoReporter(false, stream, config);
+ reporter.report('phantomjs', {
+ name: 'it didnt work',
+ passed: false,
+ error: {
+ message: 'it crapped out',
+ stack: (new Error('it crapped out')).stack
+ }
+ });
+ reporter.finish();
+ var output = stream.read().toString();
+ assert.match(output, /it didnt work/);
+ assert.match(output, /<error message=\"it crapped out\">/);
+ assert.match(output, /CDATA\[Error: it crapped out/);
+
+ assertXmlIsValid(output);
+ });
+
+ it('outputs errors without stack traces', function() {
+ var config = new Config('ci', {
+ xunit_intermediate_output: false,
+ xunit_exclude_stack: true
+ });
+ var reporter = new SolanoReporter(false, stream, config);
+ reporter.report('phantomjs', {
+ name: 'it didnt work',
+ passed: false,
+ error: {
+ message: 'it crapped out',
+ stack: (new Error('it crapped out')).stack
+ }
+ });
+ reporter.finish();
+ var output = stream.read().toString();
+ assert.match(output, /it didnt work/);
+ assert.match(output, /<error message=\"it crapped out\"\/>/);
+ assert.notMatch(output, /CDATA\[Error: it crapped out/);
+
+ assertXmlIsValid(output);
+ });
+
+ it('outputs skipped tests', function() {
+ var reporter = new SolanoReporter(false, stream, config);
+ reporter.report('phantomjs', {
+ name: 'it didnt work',
+ passed: false,
+ skipped: true
+ });
+ reporter.finish();
+ var output = stream.read().toString();
+ assert.match(output, /<skipped\/>/);
+
+ assertXmlIsValid(output);
+ });
+
+ it('skipped tests are not considered failures', function() {
+ var reporter = new SolanoReporter(false, stream, config);
+ reporter.report('phantomjs', {
+ name: 'it didnt work',
+ passed: false,
+ skipped: true
+ });
+ reporter.finish();
+ var output = stream.read().toString();
+ assert.notMatch(output, /<failure/);
+
+ assertXmlIsValid(output);
+ });
+
+ it('outputs failed tests', function() {
+ var reporter = new SolanoReporter(false, stream, config);
+ reporter.report('phantomjs', {
+ name: 'it didnt work',
+ passed: false
+ });
+ reporter.finish();
+ var output = stream.read().toString();
+ assert.match(output, /<failure/);
+
+ assertXmlIsValid(output);
+ });
+
+ it('XML escapes errors', function() {
+ var reporter = new SolanoReporter(false, stream, config);
+ reporter.report('phantomjs', {
+ name: 'it failed with quotes',
+ passed: false,
+ error: {
+ message: (new Error('<it> \"crapped\" out')).stack
+ }
+ });
+ reporter.finish();
+ var output = stream.read().toString();
+ assert.match(output, /it failed with quotes"/);
+ assert.match(output, /&lt;it> &quot;crapped&quot; out/);
+
+ assertXmlIsValid(output);
+ });
+
+ it('XML escapes messages', function() {
+ var reporter = new SolanoReporter(false, stream, config);
+ reporter.report('phantomjs', {
+ name: 'it failed with ampersands',
+ passed: false,
+ error: { message: '&&' }
+ });
+ reporter.finish();
+ var output = stream.read().toString();
+ assert.match(output, /it failed with ampersands"/);
+ assert.match(output, /&amp;&amp;/);
+
+ assertXmlIsValid(output);
+ });
+
+ it('presents valid XML with null messages', function() {
+ var reporter = new SolanoReporter(false, stream, config);
+ reporter.report('phantomjs', {
+ name: 'null',
+ passed: false,
+ error: { message: null }
+ });
+ reporter.finish();
+ var output = stream.read().toString();
+
+ assertXmlIsValid(output);
+ });
+ });
+
describe('teamcity reporter', function() {
var stream;

0 comments on commit de95e05

Please sign in to comment.