updated logging and junit output for jasmine 2.0.0

This commit is contained in:
Jarrod Overson 2014-01-28 17:12:51 -08:00
parent 12854458f2
commit dfa9182e20
8 changed files with 208 additions and 254 deletions

View File

@ -34,6 +34,12 @@ module.exports = function(grunt) {
jshintrc: '.jshintrc'
}
},
watch : {
dev : {
files : ['tasks/**/*'],
tasks : ['jasmine:pivotal:build']
}
},
jasmine: {
pivotal: {
src: 'test/fixtures/pivotal/src/**/*.js',
@ -102,6 +108,7 @@ module.exports = function(grunt) {
grunt.loadTasks('tasks');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-nodeunit');
grunt.loadNpmTasks('grunt-contrib-internal');
grunt.loadNpmTasks('grunt-contrib-connect');

View File

@ -1,4 +1,4 @@
# grunt-contrib-jasmine v0.5.1 [![Build Status](https://travis-ci.org/gruntjs/grunt-contrib-jasmine.png?branch=master)](https://travis-ci.org/gruntjs/grunt-contrib-jasmine)
# grunt-contrib-jasmine v0.5.2 [![Build Status](https://travis-ci.org/gruntjs/grunt-contrib-jasmine.png?branch=master)](https://travis-ci.org/gruntjs/grunt-contrib-jasmine)
> Run jasmine specs headlessly through PhantomJS.
@ -275,4 +275,4 @@ for more information on the RequireJS template.
Task submitted by [Jarrod Overson](http://jarrodoverson.com)
*This file was generated on Mon Sep 02 2013 11:05:17.*
*This file was generated on Tue Jan 28 2014 16:29:26.*

View File

@ -28,7 +28,9 @@
},
"dependencies": {
"grunt-lib-phantomjs": "~0.4.0",
"rimraf": "~2.1.4"
"rimraf": "~2.1.4",
"chalk": "~0.4.0",
"lodash": "~2.4.1"
},
"devDependencies": {
"grunt-contrib-internal": "~0.4.5",
@ -48,4 +50,4 @@
"headless",
"tests"
]
}
}

View File

@ -11,10 +11,12 @@ module.exports = function(grunt) {
// node api
var fs = require('fs'),
path = require('path');
path = require('path');
// npm lib
var phantomjs = require('grunt-lib-phantomjs').init(grunt);
var phantomjs = require('grunt-lib-phantomjs').init(grunt),
chalk = require('chalk'),
_ = require('lodash');
// local lib
var jasmine = require('./lib/jasmine').init(grunt, phantomjs);
@ -23,29 +25,39 @@ module.exports = function(grunt) {
var status = {};
var symbols = {
check : '✓',
error : 'X',
splat : '*'
};
//With node.js on Windows: use symbols available in terminal default fonts
//https://github.com/visionmedia/mocha/pull/641
if (process && process.platform === 'win32') {
symbols = {
check : '\u221A',
error : '\u00D7',
splat : '*'
};
}
grunt.registerMultiTask('jasmine', 'Run jasmine specs headlessly through PhantomJS.', function() {
// Merge task-specific options with these defaults.
var options = this.options({
version : '2.0.0-rc5',
version : '2.0.0',
timeout : 10000,
styles : [],
specs : [],
styles : [],
specs : [],
helpers : [],
vendor : [],
vendor : [],
outfile : '_SpecRunner.html',
host : '',
host : '',
template : __dirname + '/jasmine/templates/DefaultRunner.tmpl',
templateOptions : {},
junit: {}
});
if (options.template === 'requirejs') {
grunt.log.warn(
'The requirejs template is no longer included in grunt-contrib-jasmine core.\n' +
'Please see the https://github.com/gruntjs/grunt-contrib-jasmine README for details'
);
}
junit : {},
ignoreEmpty: grunt.option('force') === true
});
if (grunt.option('debug')) {
grunt.log.debug(options);
@ -53,8 +65,8 @@ module.exports = function(grunt) {
setup(options);
// The filter returned no spec, let's skip phantom.
if(!jasmine.buildSpecrunner(this.filesSrc, options)) {
// The filter returned no spec files so skip phantom.
if (!jasmine.buildSpecrunner(this.filesSrc, options)) {
return removePhantomListeners();
}
@ -62,27 +74,26 @@ module.exports = function(grunt) {
if (this.flags.build) return;
var done = this.async();
phantomRunner(options, function(err,status) {
phantomRunner(options, function(err, status) {
var success = !err && status.failed === 0;
if (err) grunt.log.error(err);
if (status.failed === 0) grunt.log.ok('0 failures');
else grunt.log.error(status.failed + ' failures');
if (err) {
grunt.log.error(err);
}
if (status.failed === 0) {
grunt.log.ok('0 failures');
} else {
grunt.log.error(status.failed + ' failures');
}
teardown(options, function(){
teardown(options, function() {
done(success);
});
});
});
function logWrite(text, isInline) {
text += (isInline ? '' : '\n');
status.log += text;
grunt.verbose.write(text);
}
function phantomRunner(options,cb){
function phantomRunner(options, cb){
var file = options.outfile;
if (options.host) {
@ -90,12 +101,13 @@ module.exports = function(grunt) {
file = options.host + options.outfile;
}
grunt.verbose.subhead('Testing jasmine specs via phantom').or.writeln('Testing jasmine specs via phantom');
grunt.verbose.subhead('Testing jasmine specs via phantom').or.writeln('Testing jasmine specs via PhantomJS');
grunt.log.writeln('');
phantomjs.spawn(file, {
failCode : 90,
options : options,
done : function(err){
options : options,
done : function(err){
cb(err,status);
}
});
@ -104,7 +116,10 @@ module.exports = function(grunt) {
function teardown(options, cb) {
removePhantomListeners();
if (!options.keepRunner && fs.statSync(options.outfile).isFile()) fs.unlink(options.outfile);
if (!options.keepRunner && fs.statSync(options.outfile).isFile()) {
fs.unlink(options.outfile);
}
if (!options.keepRunner) {
jasmine.cleanTemp(cb);
} else {
@ -118,35 +133,39 @@ module.exports = function(grunt) {
}
function setup(options) {
var thisRun = {};
var indentLevel = 1,
tabstop = 2,
thisRun = {},
suites = {},
currentSuite;
status = {
failed : 0,
log : ''
failed : 0
};
phantomjs.on('fail.timeout',function(){
function indent(times) {
return new Array(+times * tabstop).join(' ');
}
phantomjs.on('fail.timeout', function() {
grunt.log.writeln();
grunt.warn('PhantomJS timed out, possibly due to an unfinished async spec.', 90);
});
phantomjs.on('console',console.log.bind(console));
phantomjs.on('verbose',function(msg) {
grunt.verbose.writeln('\nlog: '.yellow + msg);
phantomjs.on('console', function(msg) {
grunt.log.writeln('\n' + chalk.yellow('log: ') + msg);
});
phantomjs.on('debug', grunt.log.debug.bind(grunt.log, 'phantomjs'));
phantomjs.on('write', grunt.log.write.bind(grunt.log));
phantomjs.on('writeln', grunt.log.writeln.bind(grunt.log));
phantomjs.on('error.onError',function(string, trace){
phantomjs.on('error.onError', function(string, trace){
if (trace && trace.length) {
grunt.log.error(string.red + ' at ');
grunt.log.error(chalk.red(string) + ' at ');
trace.forEach(function(line) {
var file = line.file.replace(/^file:/,'');
var message = grunt.util._('%s:%d %s').sprintf(path.relative('.',file), line.line, line.function);
grunt.log.error(message.red);
grunt.log.error(chalk.red(message));
});
} else {
grunt.log.error("Error caught from phantom. More info can be found by opening the Spec Runner in a browser.");
grunt.log.error("Error caught from PhantomJS. More info can be found by opening the Spec Runner in a browser.");
grunt.warn(string);
}
});
@ -156,91 +175,131 @@ module.exports = function(grunt) {
grunt.event.emit.apply(grunt.event, args);
});
phantomjs.on('jasmine.reportRunnerStarting',function() {
grunt.verbose.writeln('Starting...');
thisRun.start_time = (new Date()).getTime();
thisRun.executed_specs = 0;
thisRun.passed_specs = 0;
phantomjs.on('jasmine.jasmineStarted', function() {
grunt.verbose.writeln('Jasmine Runner Starting...');
thisRun.startTime = (new Date()).getTime();
thisRun.executedSpecs = 0;
thisRun.passedSpecs = 0;
thisRun.failedSpecs = 0;
thisRun.skippedSpecs = 0;
});
phantomjs.on('jasmine.reportSpecStarting',function(specMetadata) {
thisRun.executed_specs++;
grunt.verbose.write(specMetadata.fullName + '...');
phantomjs.on('jasmine.suiteStarted', function(suiteMetaData) {
currentSuite = suiteMetaData.id;
suites[currentSuite] = {
name : suiteMetaData.fullName,
timestamp : new Date(suiteMetaData.startTime),
errors : 0,
tests : 0,
failures : 0,
testcases : []
};
grunt.log.write(indent(indentLevel++));
grunt.log.writeln(chalk.bold(suiteMetaData.description));
});
phantomjs.on('jasmine.reportSpecResults',function(specMetadata) {
if (specMetadata.status === "passed") {
thisRun.passed_specs++;
grunt.verbose.writeln(specMetadata.description + ': ' + specMetadata.status.green);
if (!grunt.option('verbose'))
grunt.log.write('.'.green);
} else if (specMetadata.status === "failed") {
if (grunt.option('verbose'))
grunt.verbose.writeln(specMetadata.description + ': ' + specMetadata.status.red);
else {
logWrite(specMetadata.fullName + ': ' + specMetadata.status.red);
grunt.log.write('x'.red);
}
phantomjs.on('jasmine.suiteDone', function(suiteMetaData) {
suites[currentSuite].time = suiteMetaData.duration / 1000;
indentLevel--;
});
phantomjs.on('jasmine.specStarted', function(specMetaData) {
thisRun.executedSpecs++;
grunt.log.write(indent(indentLevel) + '- ' + chalk.grey(specMetaData.description));
});
phantomjs.on('jasmine.specDone', function(specMetaData) {
var specSummary = {
assertions : 0,
classname : suites[currentSuite].name,
name : specMetaData.description,
time : specMetaData.duration / 1000,
failureMessages : []
};
suites[currentSuite].tests++;
var color = 'yellow',
symbol = 'splat';
if (specMetaData.status === "passed") {
thisRun.passedSpecs++;
color = 'green';
symbol = 'check';
} else if (specMetaData.status === "failed") {
thisRun.failedSpecs++;
status.failed++;
color = 'red';
symbol = 'error';
suites[currentSuite].failures++;
suites[currentSuite].errors += specMetaData.failedExpectations.length;
specSummary.failureMessages = specMetaData.failedExpectations.map(function(error){
return error.message;
});
} else {
grunt.verbose.writeln(specMetadata.description + ': ' + specMetadata.status.yellow);
if (!grunt.option('verbose'))
grunt.log.write('P'.yellow);
thisRun.skippedSpecs++;
}
for (var i = 0; i < specMetadata.failedExpectations.length; i++) {
var item = specMetadata.failedExpectations[i];
suites[currentSuite].testcases.push(specSummary);
process.stdout.clearLine();
process.stdout.cursorTo(0);
grunt.log.writeln(
indent(indentLevel) +
chalk[color].bold(symbols[symbol]) + ' ' +
chalk.grey(specMetaData.description)
);
specMetaData.failedExpectations.forEach(function(error, i){
var specIndex = ' ('+(i+1)+')';
logWrite(' ' + item.message.red+specIndex.red);
phantomjs.emit('onError', item.message, item.stack);
}
phantomjs.emit('jasmine.testDone', specMetadata.failedExpectations.length);
grunt.log.writeln(indent(indentLevel + 1) + chalk.red(error.message + specIndex));
phantomjs.emit('onError', error.message, error.stack);
});
});
phantomjs.on('jasmine.reportRunnerResults',function(){
var dur = (new Date()).getTime() - thisRun.start_time;
var spec_str = thisRun.executed_specs + (thisRun.executed_specs === 1 ? " spec " : " specs ");
grunt.verbose.writeln('Runner finished');
if (thisRun.executed_specs === 0) {
grunt.warn('No specs executed, is there a configuration error?');
}
if (!grunt.option('verbose')) {
grunt.log.writeln('');
grunt.log.write(status.log);
}
grunt.log.writeln(spec_str + 'in ' + (dur/1000) + "s.");
});
phantomjs.on('jasmine.jasmineDone', function(){
var dur = (new Date()).getTime() - thisRun.startTime;
var specQuantity = thisRun.executedSpecs + (thisRun.executedSpecs === 1 ? " spec " : " specs ");
phantomjs.on('jasmine.testDone',function(failedAssertions) {
status.failed += failedAssertions;
});
grunt.verbose.writeln('Jasmine runner finished');
if (thisRun.executedSpecs === 0) {
// log.error will print the message but not fail the task, warn will do both.
var log = (options.ignoreEmpty) ? grunt.log.error : grunt.warn;
log('No specs executed, is there a configuration error?');
}
phantomjs.on('jasmine.reportJUnitResults',function(junitData){
if (options.junit && options.junit.path) {
var template = grunt.file.read(junitTemplate);
if (options.junit.consolidate) {
grunt.util._(junitData.consolidatedSuites).each(function(suites){
var xmlFile = path.join(options.junit.path, 'TEST-' + suites[0].name.replace(/[^\w]/g, '') + '.xml');
grunt.file.write(xmlFile, grunt.util._.template(template, { testsuites: suites}));
});
} else {
junitData.suites.forEach(function(suiteData){
var xmlFile = path.join(options.junit.path, 'TEST-' + suiteData.name.replace(/[^\w]/g, '') + '.xml');
grunt.file.write(xmlFile, grunt.util._.template(template, { testsuites: [suiteData] }));
});
}
writeJunitXml(suites);
}
grunt.log.writeln('\n' + specQuantity + 'in ' + (dur / 1000) + "s.");
});
phantomjs.on('jasmine.done',function(elapsed){
function writeJunitXml(testsuites){
var template = grunt.file.read(junitTemplate);
if (options.junit.consolidate) {
var xmlFile = path.join(options.junit.path, 'TEST-' + testsuites.suite1.name.replace(/[^\w]/g, '') + '.xml');
grunt.file.write(xmlFile, grunt.util._.template(template, { testsuites: _.values(testsuites)}));
} else {
_.forEach(testsuites, function(suiteData){
var xmlFile = path.join(options.junit.path, 'TEST-' + suiteData.name.replace(/[^\w]/g, '') + '.xml');
grunt.file.write(xmlFile, _.template(template, { testsuites: [suiteData] }));
});
}
}
phantomjs.on('jasmine.done', function(elapsed) {
phantomjs.halt();
});
phantomjs.on('jasmine.done.PhantomReporter',function(){
phantomjs.on('jasmine.done.PhantomReporter', function() {
phantomjs.emit('jasmine.done');
});
phantomjs.on('jasmine.done_fail',function(url){
phantomjs.on('jasmine.done_fail', function(url) {
grunt.log.error();
grunt.warn('PhantomJS unable to load "' + url + '" URI.', 90);
});

View File

@ -7,7 +7,7 @@ var phantom = {};
if (window._phantom) {
console.log = function(){
phantom.sendMessage('verbose', Array.prototype.slice.apply(arguments).join(', '));
phantom.sendMessage('console', Array.prototype.slice.apply(arguments).join(', '));
};
}
@ -17,6 +17,8 @@ phantom.sendMessage = function() {
if (window._phantom) {
// alerts are the communication bridge to grunt
alert( payload );
} else {
console.log(arguments);
}
};
@ -32,17 +34,18 @@ phantom.sendMessage = function() {
PhantomReporter.prototype.jasmineStarted = function() {
this.started = true;
phantom.sendMessage('jasmine.reportRunnerStarting');
phantom.sendMessage('jasmine.jasmineStarted');
};
PhantomReporter.prototype.specStarted = function(specMetadata) {
specMetadata.startTime = (new Date()).getTime();
phantom.sendMessage('jasmine.reportSpecStarting', specMetadata);
phantom.sendMessage('jasmine.specStarted', specMetadata);
};
PhantomReporter.prototype.suiteStarted = function(suiteMetadata) {
suiteMetadata.startTime = (new Date()).getTime();
}
phantom.sendMessage('jasmine.suiteStarted', suiteMetadata);
};
PhantomReporter.prototype.suites = function() {
return this.suites_;
@ -70,27 +73,15 @@ phantom.sendMessage = function() {
return this.results_;
};
PhantomReporter.prototype.resultsForSpec = function(specId) {
return this.results_[specId];
};
function map(values, f) {
var result = [];
for (var ii = 0; ii < values.length; ii++) {
result.push(f(values[ii]));
}
return result;
}
PhantomReporter.prototype.jasmineDone = function() {
this.finished = true;
phantom.sendMessage('jasmine.reportRunnerResults');
//phantom.sendMessage('jasmine.reportJUnitResults', this.generateJUnitSummary(runner));
phantom.sendMessage('jasmine.jasmineDone');
phantom.sendMessage('jasmine.done.PhantomReporter');
};
PhantomReporter.prototype.suiteDone = function(suiteMetadata) {
suiteMetadata.duration = (new Date()).getTime() - suiteMetadata.startTime;
phantom.sendMessage('jasmine.suiteDone', suiteMetadata);
};
function stringify(obj) {
@ -145,114 +136,7 @@ phantom.sendMessage = function() {
}
}
phantom.sendMessage( 'jasmine.reportSpecResults', specMetadata);
};
PhantomReporter.prototype.getFullName = function(spec) {
return getNestedSuiteName(spec.suite, ':: ') + ':: ' + spec.description;
};
PhantomReporter.prototype.resultsForSpecs = function(specIds){
var results = {};
for (var i = 0; i < specIds.length; i++) {
var specId = specIds[i];
results[specId] = this.summarizeResult_(this.results_[specId]);
}
return results;
};
PhantomReporter.prototype.summarizeResult_ = function(result){
var summaryMessages = [];
var messagesLength = result.messages.length;
for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
var resultMessage = result.messages[messageIndex];
summaryMessages.push({
text: resultMessage.type === 'log' ? resultMessage.toString() : jasmine.undefined,
passed: resultMessage.passed ? resultMessage.passed() : true,
type: resultMessage.type,
message: resultMessage.message,
trace: {
stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
}
});
}
return {
result : result.result,
messages : summaryMessages
};
};
function getNestedSuiteName(suite, sep) {
var names = [];
while (suite) {
names.unshift(suite.description);
suite = suite.parentSuite;
}
return names.join(sep ? sep : ' ');
}
function getTopLevelSuiteId(suite) {
var id;
while (suite) {
id = suite.id;
suite = suite.parentSuite;
}
return id;
}
PhantomReporter.prototype.generateJUnitSummary = function(runner) {
var consolidatedSuites = {},
suites = map(runner.suites(), function(suite) {
var failures = 0;
var testcases = map(suite.specs(), function(spec) {
var failureMessages = [];
var specResults = spec.results();
var resultsItems = specResults.items_;
var resultsItemCount = resultsItems.length;
if (specResults.failedCount) {
failures++;
for (var ii = 0; ii < resultsItemCount; ii++) {
var expectation = resultsItems[ii];
if (!expectation.passed()) {
failureMessages.push(expectation.message);
}
}
}
return {
assertions: resultsItemCount,
className: getNestedSuiteName(spec.suite),
name: spec.description,
time: spec.duration / 1000,
failureMessages: failureMessages
};
});
var 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: testcases,
failures: failures
};
if (suite.parentSuite) {
consolidatedSuites[getTopLevelSuiteId(suite)].push(data);
} else {
consolidatedSuites[suite.id] = [data];
}
return data;
});
return {
suites: suites,
consolidatedSuites: consolidatedSuites
};
phantom.sendMessage( 'jasmine.specDone', specMetadata);
};
jasmine.getEnv().addReporter( new PhantomReporter() );

View File

@ -3,7 +3,7 @@
<% 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 assertions="<%= testcase.assertions %>" classname="<%- testcase.classname %>" name="<%- testcase.name %>" time="<%= testcase.time %>">
<% testcase.failureMessages.forEach(function(message) { %>
<failure><%= message %></failure>
<% }) %>

View File

@ -7,7 +7,8 @@ exports.init = function(grunt, phantomjs) {
path = require('path');
// npm
var rimraf = require('rimraf');
var rimraf = require('rimraf'),
_ = require('lodash');
var baseDir = '.',
tempDir = '.grunt/grunt-contrib-jasmine';
@ -41,7 +42,7 @@ exports.init = function(grunt, phantomjs) {
// Let's filter through the spec files here,
// there's no need to go on if no specs matches
if(gruntfilter) {
if (gruntfilter) {
filteredSpecs = specFilter(gruntfilter, filteredSpecs);
if(filteredSpecs.length === 0) {
@ -80,16 +81,17 @@ exports.init = function(grunt, phantomjs) {
var context = {
temp : tempDir,
css : exports.getRelativeFileList(outdir, jasmineCss, { nonull : true }),
outfile: outfile,
css : exports.getRelativeFileList(outdir, jasmineCss, { nonull : true }),
scripts : {
polyfills : exports.getRelativeFileList(outdir, polyfills),
jasmine : exports.getRelativeFileList(outdir, jasmineCore),
helpers : exports.getRelativeFileList(outdir, options.helpers, { nonull : true }),
specs : filteredSpecs,
src : exports.getRelativeFileList(outdir, src, { nonull : true }),
vendor : exports.getRelativeFileList(outdir, options.vendor, { nonull : true }),
jasmine : exports.getRelativeFileList(outdir, jasmineCore),
helpers : exports.getRelativeFileList(outdir, options.helpers, { nonull : true }),
specs : filteredSpecs,
src : exports.getRelativeFileList(outdir, src, { nonull : true }),
vendor : exports.getRelativeFileList(outdir, options.vendor, { nonull : true }),
reporters : exports.getRelativeFileList(outdir, reporters),
boot : exports.getRelativeFileList(outdir, tempDir + '/boot.js')
boot : exports.getRelativeFileList(outdir, tempDir + '/boot.js')
},
options : options.templateOptions || {}
};
@ -105,7 +107,7 @@ exports.init = function(grunt, phantomjs) {
} else {
grunt.file.copy(options.template, specrunner, {
process : function(src) {
source = grunt.util._.template(src, context);
source = _.template(src, context);
return source;
}
});
@ -114,7 +116,7 @@ exports.init = function(grunt, phantomjs) {
return source;
};
exports.getRelativeFileList = function (outdir, patterns, options) {
exports.getRelativeFileList = function(outdir, patterns, options) {
var files = [];
patterns = patterns instanceof Array ? patterns : [ patterns ];
options = options || {};
@ -161,7 +163,7 @@ exports.init = function(grunt, phantomjs) {
}
}
filteredArray = grunt.util._.uniq(scriptSpecs);
filteredArray = _.uniq(scriptSpecs);
}
return filteredArray;

View File

@ -10,7 +10,7 @@ Player.prototype.pause = function() {
};
Player.prototype.resume = function() {
console.log('in resume');
console.log('Console logging test')
if (this.isPlaying) {
throw new Error("song is already playing");
}