This commit is contained in:
Jarrod Overson 2013-01-08 10:00:13 -08:00
commit 81d45689d0
5 changed files with 253 additions and 17 deletions

View File

@ -62,6 +62,14 @@ Default: `{}`
These options will be passed to your template as an 'options' hash so that you can provide settings to your template.
## options.junit
Type: `Object`
Default: `{}`
Set `options.junit.path` to generate JUnit compatible XML from the task (for use in a CI system such as Jenkins).
Set `options.junit.consolidate` to consolidate the generated XML files so that there is one file per top level suite.
# Flags
Name: `build`

View File

@ -32,7 +32,8 @@ module.exports = function(grunt) {
var runners = {
default : __dirname + '/jasmine/templates/DefaultRunner.tmpl',
requirejs : __dirname + '/jasmine/templates/RequireJSRunner.tmpl'
requirejs : __dirname + '/jasmine/templates/RequireJSRunner.tmpl',
junit : __dirname + '/jasmine/templates/JUnit.tmpl'
};
var runnerOptions = {
@ -53,7 +54,8 @@ module.exports = function(grunt) {
host : '',
template: 'default',
templateOptions : {},
phantomjs : {}
phantomjs : {},
junit: {}
});
grunt.util._.defaults(options.templateOptions, runnerOptions[options.template] || {});
@ -143,13 +145,14 @@ module.exports = function(grunt) {
grunt.event.emit.apply(grunt.event, args);
});
phantomjs.on('jasmine.writeFile',function(type,filename, xml){
var dir = options[type] && options[type].output;
if (dir) {
grunt.file.mkdir(dir);
grunt.file.write(path.join(dir, filename), xml);
}
});
// Not used?
// phantomjs.on('jasmine.writeFile',function(type,filename, xml){
// var dir = options[type] && options[type].output;
// if (dir) {
// grunt.file.mkdir(dir);
// grunt.file.write(path.join(dir, filename), xml);
// }
// });
phantomjs.on('jasmine.reportRunnerStarting',function(suites) {
@ -203,6 +206,46 @@ module.exports = function(grunt) {
status.skipped += skippedAssertions;
});
phantomjs.on('jasmine.reportJUnitResults',function(junitData){
if (options.junit && options.junit.path) {
if (options.junit.consolidate) {
grunt.util._(junitData.consolidatedSuites).each(
function(suites)
{
grunt.file.copy(runners.junit, path.join(options.junit.path, 'TEST-' + suites[0].name.replace(/[^\w]/g, '') + '.xml'), {
process: function(src) {
return grunt.util._.template(
src,
{
testsuites: suites
}
);
}
});
}
);
} else {
junitData.suites.forEach(
function(suiteData)
{
grunt.file.copy(runners.junit, path.join(options.junit.path, 'TEST-' + suiteData.name.replace(/[^\w]/g, '') + '.xml'), {
process: function(src) {
return grunt.util._.template(
src,
{
testsuites: [suiteData]
}
);
}
});
}
);
}
}
});
phantomjs.on('jasmine.done',function(elapsed){
phantomjs.halt();
status.duration = elapsed;
@ -219,6 +262,3 @@ module.exports = function(grunt) {
}
};

View File

@ -0,0 +1,94 @@
/*global jasmine */
(function()
{
'use strict';
function getNestedSuiteName(suite)
{
var names = [];
while (suite) {
names.unshift(suite.description);
suite = suite.parentSuite;
}
return names.join(' ');
}
function getTopLevelSuiteId(suite)
{
var id;
while (suite) {
id = suite.id;
suite = suite.parentSuite;
}
return id;
}
jasmine.JUnitDataReporter = function()
{
};
jasmine.JUnitDataReporter.prototype = {
reportRunnerStarting: function(runner) {
},
reportRunnerResults: function(runner) {
var suitesById = {},
suites = runner.suites().map(
function(suite)
{
var failures = 0,
data = {
topLevelSuiteId: getTopLevelSuiteId(suite),
name: getNestedSuiteName(suite),
time: suite.duration / 1000,
timestamp: suite.timestamp,
tests: suite.specs().length,
errors: 0, // TODO: These exist in the JUnit XML but not sure how they map to jasmine things
testcases: suite.specs().map(
function(spec)
{
var failureMessages = [];
if (spec.results().failedCount) {
failures ++;
spec.results().items_.forEach(
function(expectation)
{
if (!expectation.passed()) {
failureMessages.push(expectation.message);
}
}
);
}
return {
assertions: spec.results().items_.length,
className: getNestedSuiteName(spec.suite),
name: spec.description,
time: spec.duration / 1000,
failureMessages: failureMessages
};
}
)
};
data.failures = failures;
suitesById[suite.id] = data;
return data;
}
);
console.log('Suites:', suites);
},
reportSuiteResults: function(suite) {
suite.timestamp = new Date();
suite.duration = suite.timestamp.getTime() - suite.specs()[0].startTime;
},
reportSpecStarting: function(spec) {
spec.startTime = (new Date()).getTime();
},
reportSpecResults: function(spec) {
spec.duration = (new Date()).getTime() - spec.startTime;
},
log: function(str) {
console.log(str);
}
};
}());

View File

@ -40,6 +40,7 @@
};
PhantomReporter.prototype.reportSpecStarting = function(spec) {
spec.startTime = (new Date()).getTime();
var message = {
suite : {
description : spec.suite.description
@ -84,14 +85,19 @@
var specIds = runner.specs().map(function(a){return a.id;});
var summary = this.resultsForSpecs(specIds);
phantom.sendMessage('jasmine.reportRunnerResults',summary);
phantom.sendMessage('jasmine.reportJUnitResults', this.generateJUnitSummary_(runner));
phantom.sendMessage('jasmine.done.PhantomReporter');
};
PhantomReporter.prototype.reportSuiteResults = function(suite) {
if (suite.specs().length) {
suite.timestamp = new Date();
suite.duration = suite.timestamp.getTime() - suite.specs()[0].startTime;
phantom.sendMessage('jasmine.reportSuiteResults',{
description : suite.description,
results : suite.results()
});
}
};
function stringify(obj) {
@ -130,6 +136,7 @@
}
PhantomReporter.prototype.reportSpecResults = function(spec) {
spec.duration = (new Date()).getTime() - spec.startTime;
var _results = spec.results();
var results = {
description : _results.description,
@ -189,5 +196,78 @@
};
};
function getNestedSuiteName(suite)
{
var names = [];
while (suite) {
names.unshift(suite.description);
suite = suite.parentSuite;
}
return names.join(' ');
}
function getTopLevelSuiteId(suite)
{
var id;
while (suite) {
id = suite.id;
suite = suite.parentSuite;
}
return id;
}
PhantomReporter.prototype.generateJUnitSummary_ = function(runner) {
var consolidatedSuites = {},
suites = runner.suites().map(
function(suite)
{
var failures = 0,
data = {
name: getNestedSuiteName(suite),
time: suite.duration / 1000,
timestamp: suite.timestamp,
tests: suite.specs().length,
errors: 0, // TODO: These exist in the JUnit XML but not sure how they map to jasmine things
testcases: suite.specs().map(
function(spec)
{
var failureMessages = [];
if (spec.results().failedCount) {
failures ++;
spec.results().items_.forEach(
function(expectation)
{
if (!expectation.passed()) {
failureMessages.push(expectation.message);
}
}
);
}
return {
assertions: spec.results().items_.length,
className: getNestedSuiteName(spec.suite),
name: spec.description,
time: spec.duration / 1000,
failureMessages: failureMessages
};
}
)
};
data.failures = failures;
if (suite.parentSuite) {
consolidatedSuites[getTopLevelSuiteId(suite)].push(data);
} else {
consolidatedSuites[suite.id] = [data];
}
return data;
}
);
return {
suites: suites,
consolidatedSuites: consolidatedSuites
};
};
jasmine.getEnv().addReporter( new PhantomReporter() );
}());

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<testsuites>
<% testsuites.forEach(function(testsuite) { %>
<testsuite name="<%- testsuite.name %>" errors="<%= testsuite.errors %>" tests="<%= testsuite.tests %>" failures="<%= testsuite.failures %>" time="<%= testsuite.time %>" timestamp="<%= testsuite.timestamp %>">
<% testsuite.testcases.forEach(function(testcase) { %>
<testcase assertions="<%= testcase.assertions %>" classname="<%- testcase.className %>" name="<%- testcase.name %>" time="<%= testcase.time %>">
<% testcase.failureMessages.forEach(function(message) { %>
<failure><%= message %></failure>
<% }) %>
</testcase>
<% }) %>
</testsuite>
<% }) %>
</testsuites>