Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat junitreporter #4

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage

# junit reporter directory
HeadlessChrome*

# nyc test coverage
.nyc_output

Expand Down Expand Up @@ -57,3 +60,5 @@ typings/
# dotenv environment variables file
.env

# for those who use idea, ignore project files
.idea
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ module.exports = function(config) {
'src/**/*.js': ['coverage'] // coverage is loaded from karma-coverage by karma-sharding
},

// sharding replaces the coverage reporter inline to allow the preprocessor to run
// sharding replaces the coverage and junit reporters inline to allow the preprocessor to run
// preprocessor:coverage looks for reporter:coverage otherwise it would use a unique name
reporters: ['progress', 'coverage'],
reporters: ['progress', 'coverage', 'junit'],

browsers: ['ChromeHeadless', 'ChromeHeadless'] // this will split the tests into two sets
});
Expand Down
9 changes: 8 additions & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = function(config) {
plugins: [
'karma-chrome-launcher',
'karma-coverage',
'karma-junit-reporter',
'karma-jasmine',
require('.')
],
Expand Down Expand Up @@ -45,7 +46,7 @@ module.exports = function(config) {
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'coverage'],
reporters: ['progress', 'coverage', 'junit'],


// configuration for karma-coverage
Expand All @@ -59,6 +60,12 @@ module.exports = function(config) {
},


junitReporter: {
outputFile: 'test-results.xml',
suite: ''
},


// web server port
port: 9876,

Expand Down
10 changes: 9 additions & 1 deletion lib/framework.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ function setupCoverageReporting(fullConfig) {
fullConfig.coverageReporter.browserId = 'name';
}

function setupJunitReporting(fullConfig) {
// ensure that the junit reporter aggregates junit reporting based on browser.name
fullConfig.junitReporter = fullConfig.junitReporter || {};
fullConfig.junitReporter.browserId = 'name';
}

function setupSets(config, basePath, files) {
config.sets = config.getSets(config, basePath, files);
config.performSharding = _.last(config.sets).length;
Expand All @@ -42,6 +48,7 @@ function getSets(config, basePath, files) {
function setupSharding(config, fullConfig, log) {
if (!config.performSharding) {
fullConfig.coverageReporter.browserId = 'id'; // reset coverage back to their default
fullConfig.junitReporter.browserId = 'id'; // reset junit back to their default
fullConfig.browsers = fullConfig.browsers.filter(function(item, pos, arr) {
return arr.indexOf(item) === pos;
});
Expand Down Expand Up @@ -79,10 +86,11 @@ function generateEmitter(emitter, fullConfig, config, log) {
};
}

module.exports = function(/* config */fullConfig, emitter, logger) {
module.exports = function (/* config */fullConfig, emitter, logger) {
var log = logger.create('framework:karma-sharding');
var config = getConfig(fullConfig);
setupMiddleware(fullConfig);
setupJunitReporting(fullConfig);
setupCoverageReporting(fullConfig);
setBrowserCount(config, fullConfig.browsers, log);
// Intercepting the file_list_modified event as Vojta Jina describes here:
Expand Down
9 changes: 9 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ let coverage;
try { coverage = require('karma-coverage'); }
catch (er) { coverage = {}; }

let junit;
try { junit = require('karma-junit-reporter'); }
catch (er) { junit = {}; }

const sharding = {
'framework:sharding': ['type', require('./framework')],
'middleware:sharding': ['factory', require('./middleware')]
Expand All @@ -14,4 +18,9 @@ if (coverage['reporter:coverage']) {
sharding['reporter:coverage'] = ['type', require('./reporter')];
}

// if karma-junit-reporter is loaded override reporter:junit with our version
if (junit['reporter:junit']) {
sharding['reporter:junit'] = require('./junitReporter')['reporter:junit'];
}

module.exports = Object.assign({}, coverage, sharding);
234 changes: 234 additions & 0 deletions lib/junitReporter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
var os = require('os');
var path = require('path');
var fs = require('fs');
var builder = require('xmlbuilder');
var pathIsAbsolute = require('path-is-absolute');

// concatenate test suite(s) and test description by default
function defaultNameFormatter (browser, result) {
return result.suite.join(' ') + ' ' + result.description;
}

var JUnitReporter = function (baseReporterDecorator, config, logger, helper, formatError) {
var log = logger.create('reporter.junit');

// START OF CHANGE NEEDED ONLY FOR KARMA-SHARDING
log.info('Using karma-sharding junit reporter overlay.');
// END OF CHANGE NEEDED ONLY FOR KARMA-SHARDING

var reporterConfig = config.junitReporter || {};
var pkgName = reporterConfig.suite || '';
var outputDir = reporterConfig.outputDir;
var outputFile = reporterConfig.outputFile;
var useBrowserName = reporterConfig.useBrowserName;
var nameFormatter = reporterConfig.nameFormatter || defaultNameFormatter;
var classNameFormatter = reporterConfig.classNameFormatter;
var properties = reporterConfig.properties;

var suites = {}; // CHANGED BY KARMA-SHARDING
var pendingFileWritings = 0;
var fileWritingFinished = function () {};
var allMessages = [];

var aggregator = reporterConfig.browserId || 'id'; // ADDED BY KARMA-SHARDING
var allBrowsers; // ADDED BY KARMA-SHARDING

if (outputDir == null) {
outputDir = '.';
}

outputDir = helper.normalizeWinPath(path.resolve(config.basePath, outputDir)) + path.sep;

if (typeof useBrowserName === 'undefined') {
useBrowserName = true;
}

baseReporterDecorator(this);

this.adapters = [
function (msg) {
allMessages.push(msg);
}
];

var initializeXmlForBrowser = function (browser) {
var timestamp = (new Date()).toISOString().substr(0, 19);
var suite = suites[browser[aggregator]] = builder.create('testsuite'); // CHANGED BY KARMA-SHARDING
suite.att('name', browser.name)
.att('package', pkgName)
.att('timestamp', timestamp)
.att('id', 0)
.att('hostname', os.hostname());

var propertiesElement = suite.ele('properties');
propertiesElement.ele('property', {name: 'browser.fullName', value: browser.fullName});

// add additional properties passed in through the config
for (var property in properties) {
if (properties.hasOwnProperty(property)) {
propertiesElement.ele('property', {name: property, value: properties[property]});
}
}
};

var writeXmlForSuite = function (browserName, suiteKey) { // CHANGED BY KARMA-SHARDING
var safeBrowserName = browserName.replace(/ /g, '_'); // CHANGED BY KARMA-SHARDING
var newOutputFile;
if (outputFile && pathIsAbsolute(outputFile)) {
newOutputFile = outputFile;
} else if (outputFile != null) {
var dir = useBrowserName ? path.join(outputDir, safeBrowserName)
: outputDir;
newOutputFile = path.join(dir, outputFile);
} else if (useBrowserName) {
newOutputFile = path.join(outputDir, 'TESTS-' + safeBrowserName + '.xml');
} else {
newOutputFile = path.join(outputDir, 'TESTS.xml');
}

var xmlToOutput = suites[suiteKey]; // CHANGED BY KARMA-SHARDING
if (!xmlToOutput) {
return; // don't die if browser didn't start
}

pendingFileWritings++;
helper.mkdirIfNotExists(path.dirname(newOutputFile), function () {
fs.writeFile(newOutputFile, xmlToOutput.end({pretty: true}), function (err) {
if (err) {
log.warn('Cannot write JUnit xml\n\t' + err.message);
} else {
log.debug('JUnit results written to "%s".', newOutputFile);
}

if (!--pendingFileWritings) {
fileWritingFinished();
}
});
});
};

var getClassName = function (browser, result) {
var browserName = browser.name.replace(/ /g, '_').replace(/\./g, '_') + '.';

return (useBrowserName ? browserName : '') + (pkgName ? pkgName + '.' : '') + result.suite[0];
};

// "run_start" - a test run is beginning for all browsers
this.onRunStart = function (browsers) {
// TODO(vojta): remove once we don't care about Karma 0.10
browsers.forEach(initializeXmlForBrowser);
allBrowsers = browsers; // ADDED BY KARMA-SHARDING
};

// "browser_start" - a test run is beginning in _this_ browser
this.onBrowserStart = function (browser) {
initializeXmlForBrowser(browser);
};

// "browser_complete" - a test run has completed in _this_ browser
// START OF CONTENT CHANGED BY KARMA-SHARDING
this.onBrowserComplete = function () {
// moved to onRunComplete
};
// END OF CONTENT CHANGED BY KARMA-SHARDING

// START OF CONTENT ADDED BY KARMA-SHARDING
function createSuiteStats() {
var suiteStats = {};
allBrowsers.forEach(function (browser) {
var suiteKey = browser[aggregator];
var suiteStat = suiteStats[suiteKey];
if (!suiteStat) {
suiteStat = suiteStats[suiteKey] = {
tests: 0,
errors: 0,
failures: 0,
time: 0,
browsers: [],
suite: suites[suiteKey]
};
}
suiteStat.browsers.push(browser);
var result = browser.lastResult;
if (result) {
suiteStat.tests += result.total ? result.total : 0;
suiteStat.errors += result.disconnected || result.error ? 1 : 0;
suiteStat.failures += result.failed ? result.failed : 0;
suiteStat.time = Math.max(suiteStat.time, (result.netTime || 0) / 1000);
}
});
return suiteStats;
}
// END OF CONTENT ADDED BY KARMA-SHARDING

// "run_complete" - a test run has completed on all browsers
this.onRunComplete = function () {
// START OF CONTENT CHANGED BY KARMA-SHARDING
var suiteStats = createSuiteStats();
for (var suiteKey in suiteStats) {
var suiteStat = suiteStats[suiteKey];
var suite = suiteStat.suite;

if (!suite) {
continue; // don't die if browser didn't start
}

suite.att('tests', suiteStat.tests);
suite.att('errors', suiteStat.errors);
suite.att('failures', suiteStat.failures);
suite.att('time', suiteStat.time);

suite.ele('system-out').dat(allMessages.join() + '\n');
suite.ele('system-err');

writeXmlForSuite(suiteStat.browsers[0].name, suiteKey);

// Release memory held by the test suite.
suites[suiteKey] = null;
}
allMessages.length = 0;
allBrowsers = undefined;

// END OF CONTENT CHANGED BY KARMA-SHARDING
};

this.specSuccess = this.specSkipped = this.specFailure = function (browser, result) {
var testsuite = suites[browser[aggregator]]; // CHANGED BY KARMA-SHARDING

if (!testsuite) {
return;
}

var spec = testsuite.ele('testcase', {
name: nameFormatter(browser, result),
time: ((result.time || 0) / 1000),
classname: (typeof classNameFormatter === 'function' ? classNameFormatter : getClassName)(browser, result)
});

if (result.skipped) {
spec.ele('skipped');
}

if (!result.success) {
result.log.forEach(function (err) {
spec.ele('failure', {type: ''}, formatError(err));
});
}
};

// wait for writing all the xml files, before exiting
this.onExit = function (done) {
if (pendingFileWritings) {
fileWritingFinished = done;
} else {
done();
}
};
};

JUnitReporter.$inject = ['baseReporterDecorator', 'config', 'logger', 'helper', 'formatError'];

// PUBLISH DI MODULE
module.exports = {
'reporter:junit': ['type', JUnitReporter]
};
4 changes: 4 additions & 0 deletions lib/reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ function isAbsolute (file) {
var CoverageReporter = function (rootConfig, helper, logger, emitter) {
var log = logger.create('coverage');

// START OF CHANGE NEEDED ONLY FOR KARMA-SHARDING
log.info('Using karma-sharding coverage reporter overlay.');
// END OF CHANGE NEEDED ONLY FOR KARMA-SHARDING

// Instance variables
// ------------------

Expand Down
Loading