2 Copyright (c) 2012, Yahoo! Inc. All rights reserved.
3 Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
6 var path = require('path'),
7 util = require('util'),
8 Report = require('./index'),
9 FileWriter = require('../util/file-writer'),
10 TreeSummarizer = require('../util/tree-summarizer'),
11 utils = require('../object-utils');
14 * a `Report` implementation that produces a cobertura-style XML file that conforms to the
15 * http://cobertura.sourceforge.net/xml/coverage-04.dtd DTD.
20 * var report = require('istanbul').Report.create('cobertura');
22 * @class CoberturaReport
26 * @param {Object} opts optional
27 * @param {String} [opts.dir] the directory in which to the cobertura-coverage.xml will be written
29 function CoberturaReport(opts) {
32 this.projectRoot = process.cwd();
33 this.dir = opts.dir || this.projectRoot;
34 this.file = opts.file || this.getDefaultConfig().file;
38 CoberturaReport.TYPE = 'cobertura';
39 util.inherits(CoberturaReport, Report);
41 function asJavaPackage(node) {
42 return node.displayShortName().
48 function asClassName(node) {
49 /*jslint regexp: true */
50 return node.fullPath().replace(/.*[\\\/]/, '');
53 function quote(thing) {
54 return '"' + thing + '"';
58 return ' ' + n + '=' + quote(v) + ' ';
61 function branchCoverageByLine(fileCoverage) {
62 var branchMap = fileCoverage.branchMap,
63 branches = fileCoverage.b,
65 Object.keys(branchMap).forEach(function (k) {
66 var line = branchMap[k].line,
67 branchData = branches[k];
68 ret[line] = ret[line] || [];
69 ret[line].push.apply(ret[line], branchData);
71 Object.keys(ret).forEach(function (k) {
72 var dataArray = ret[k],
73 covered = dataArray.filter(function (item) { return item > 0; }),
74 coverage = covered.length / dataArray.length * 100;
75 ret[k] = { covered: covered.length, total: dataArray.length, coverage: coverage };
80 function addClassStats(node, fileCoverage, writer, projectRoot) {
81 fileCoverage = utils.incrementIgnoredTotals(fileCoverage);
83 var metrics = node.metrics,
84 branchByLine = branchCoverageByLine(fileCoverage),
88 writer.println('\t\t<class' +
89 attr('name', asClassName(node)) +
90 attr('filename', path.relative(projectRoot, node.fullPath())) +
91 attr('line-rate', metrics.lines.pct / 100.0) +
92 attr('branch-rate', metrics.branches.pct / 100.0) +
95 writer.println('\t\t<methods>');
96 fnMap = fileCoverage.fnMap;
97 Object.keys(fnMap).forEach(function (k) {
98 var name = fnMap[k].name,
99 hits = fileCoverage.f[k];
105 attr('signature', '()V') + //fake out a no-args void return
109 //Add the function definition line and hits so that jenkins cobertura plugin records method hits
113 attr('number', fnMap[k].line) +
114 attr('hits', fileCoverage.f[k]) +
119 writer.println('\t\t\t</method>');
122 writer.println('\t\t</methods>');
124 writer.println('\t\t<lines>');
125 lines = fileCoverage.l;
126 Object.keys(lines).forEach(function (k) {
127 var str = '\t\t\t<line' +
129 attr('hits', lines[k]),
130 branchDetail = branchByLine[k];
133 str += attr('branch', false);
135 str += attr('branch', true) +
136 attr('condition-coverage', branchDetail.coverage +
137 '% (' + branchDetail.covered + '/' + branchDetail.total + ')');
139 writer.println(str + '/>');
141 writer.println('\t\t</lines>');
143 writer.println('\t\t</class>');
146 function walk(node, collector, writer, level, projectRoot) {
149 metrics = node.metrics;
150 writer.println('<?xml version="1.0" ?>');
151 writer.println('<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">');
152 writer.println('<coverage' +
153 attr('lines-valid', metrics.lines.total) +
154 attr('lines-covered', metrics.lines.covered) +
155 attr('line-rate', metrics.lines.pct / 100.0) +
156 attr('branches-valid', metrics.branches.total) +
157 attr('branches-covered', metrics.branches.covered) +
158 attr('branch-rate', metrics.branches.pct / 100.0) +
159 attr('timestamp', Date.now()) +
160 'complexity="0" version="0.1">');
161 writer.println('<sources>');
162 writer.println('\t<source>' + projectRoot + '</source>');
163 writer.println('</sources>');
164 writer.println('<packages>');
166 if (node.packageMetrics) {
167 metrics = node.packageMetrics;
168 writer.println('\t<package' +
169 attr('name', asJavaPackage(node)) +
170 attr('line-rate', metrics.lines.pct / 100.0) +
171 attr('branch-rate', metrics.branches.pct / 100.0) +
173 writer.println('\t<classes>');
174 node.children.filter(function (child) { return child.kind !== 'dir'; }).
175 forEach(function (child) {
176 addClassStats(child, collector.fileCoverageFor(child.fullPath()), writer, projectRoot);
178 writer.println('\t</classes>');
179 writer.println('\t</package>');
181 node.children.filter(function (child) { return child.kind === 'dir'; }).
182 forEach(function (child) {
183 walk(child, collector, writer, level + 1, projectRoot);
187 writer.println('</packages>');
188 writer.println('</coverage>');
192 Report.mix(CoberturaReport, {
193 synopsis: function () {
194 return 'XML coverage report that can be consumed by the cobertura tool';
196 getDefaultConfig: function () {
197 return { file: 'cobertura-coverage.xml' };
199 writeReport: function (collector, sync) {
200 var summarizer = new TreeSummarizer(),
201 outputFile = path.join(this.dir, this.file),
202 writer = this.opts.writer || new FileWriter(sync),
203 projectRoot = this.projectRoot,
208 collector.files().forEach(function (key) {
209 summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key)));
211 tree = summarizer.getTreeSummary();
213 writer.on('done', function () { that.emit('done'); });
214 writer.writeFile(outputFile, function (contentWriter) {
215 walk(root, collector, contentWriter, 0, projectRoot);
221 module.exports = CoberturaReport;