Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80680 views
1
/*
2
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
3
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
4
*/
5
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');
12
13
/**
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.
16
*
17
* Usage
18
* -----
19
*
20
* var report = require('istanbul').Report.create('cobertura');
21
*
22
* @class CoberturaReport
23
* @module report
24
* @extends Report
25
* @constructor
26
* @param {Object} opts optional
27
* @param {String} [opts.dir] the directory in which to the cobertura-coverage.xml will be written
28
*/
29
function CoberturaReport(opts) {
30
Report.call(this);
31
opts = opts || {};
32
this.projectRoot = process.cwd();
33
this.dir = opts.dir || this.projectRoot;
34
this.file = opts.file || this.getDefaultConfig().file;
35
this.opts = opts;
36
}
37
38
CoberturaReport.TYPE = 'cobertura';
39
util.inherits(CoberturaReport, Report);
40
41
function asJavaPackage(node) {
42
return node.displayShortName().
43
replace(/\//g, '.').
44
replace(/\\/g, '.').
45
replace(/\.$/, '');
46
}
47
48
function asClassName(node) {
49
/*jslint regexp: true */
50
return node.fullPath().replace(/.*[\\\/]/, '');
51
}
52
53
function quote(thing) {
54
return '"' + thing + '"';
55
}
56
57
function attr(n, v) {
58
return ' ' + n + '=' + quote(v) + ' ';
59
}
60
61
function branchCoverageByLine(fileCoverage) {
62
var branchMap = fileCoverage.branchMap,
63
branches = fileCoverage.b,
64
ret = {};
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);
70
});
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 };
76
});
77
return ret;
78
}
79
80
function addClassStats(node, fileCoverage, writer, projectRoot) {
81
var metrics = node.metrics,
82
branchByLine = branchCoverageByLine(fileCoverage),
83
fnMap,
84
lines;
85
86
writer.println('\t\t<class' +
87
attr('name', asClassName(node)) +
88
attr('filename', path.relative(projectRoot, node.fullPath())) +
89
attr('line-rate', metrics.lines.pct / 100.0) +
90
attr('branch-rate', metrics.branches.pct / 100.0) +
91
'>');
92
93
writer.println('\t\t<methods>');
94
fnMap = fileCoverage.fnMap;
95
Object.keys(fnMap).forEach(function (k) {
96
var name = fnMap[k].name,
97
hits = fileCoverage.f[k];
98
99
writer.println(
100
'\t\t\t<method' +
101
attr('name', name) +
102
attr('hits', hits) +
103
attr('signature', '()V') + //fake out a no-args void return
104
'>'
105
);
106
107
//Add the function definition line and hits so that jenkins cobertura plugin records method hits
108
writer.println(
109
'\t\t\t\t<lines>' +
110
'<line' +
111
attr('number', fnMap[k].line) +
112
attr('hits', fileCoverage.f[k]) +
113
'/>' +
114
'</lines>'
115
);
116
117
writer.println('\t\t\t</method>');
118
119
});
120
writer.println('\t\t</methods>');
121
122
writer.println('\t\t<lines>');
123
lines = fileCoverage.l;
124
Object.keys(lines).forEach(function (k) {
125
var str = '\t\t\t<line' +
126
attr('number', k) +
127
attr('hits', lines[k]),
128
branchDetail = branchByLine[k];
129
130
if (!branchDetail) {
131
str += attr('branch', false);
132
} else {
133
str += attr('branch', true) +
134
attr('condition-coverage', branchDetail.coverage +
135
'% (' + branchDetail.covered + '/' + branchDetail.total + ')');
136
}
137
writer.println(str + '/>');
138
});
139
writer.println('\t\t</lines>');
140
141
writer.println('\t\t</class>');
142
}
143
144
function walk(node, collector, writer, level, projectRoot) {
145
var metrics;
146
if (level === 0) {
147
metrics = node.metrics;
148
writer.println('<?xml version="1.0" ?>');
149
writer.println('<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">');
150
writer.println('<coverage' +
151
attr('lines-valid', metrics.lines.total) +
152
attr('lines-covered', metrics.lines.covered) +
153
attr('line-rate', metrics.lines.pct / 100.0) +
154
attr('branches-valid', metrics.branches.total) +
155
attr('branches-covered', metrics.branches.covered) +
156
attr('branch-rate', metrics.branches.pct / 100.0) +
157
attr('timestamp', Date.now()) +
158
'complexity="0" version="0.1">');
159
writer.println('<sources>');
160
writer.println('\t<source>' + projectRoot + '</source>');
161
writer.println('</sources>');
162
writer.println('<packages>');
163
}
164
if (node.packageMetrics) {
165
metrics = node.packageMetrics;
166
writer.println('\t<package' +
167
attr('name', asJavaPackage(node)) +
168
attr('line-rate', metrics.lines.pct / 100.0) +
169
attr('branch-rate', metrics.branches.pct / 100.0) +
170
'>');
171
writer.println('\t<classes>');
172
node.children.filter(function (child) { return child.kind !== 'dir'; }).
173
forEach(function (child) {
174
addClassStats(child, collector.fileCoverageFor(child.fullPath()), writer, projectRoot);
175
});
176
writer.println('\t</classes>');
177
writer.println('\t</package>');
178
}
179
node.children.filter(function (child) { return child.kind === 'dir'; }).
180
forEach(function (child) {
181
walk(child, collector, writer, level + 1, projectRoot);
182
});
183
184
if (level === 0) {
185
writer.println('</packages>');
186
writer.println('</coverage>');
187
}
188
}
189
190
Report.mix(CoberturaReport, {
191
synopsis: function () {
192
return 'XML coverage report that can be consumed by the cobertura tool';
193
},
194
getDefaultConfig: function () {
195
return { file: 'cobertura-coverage.xml' };
196
},
197
writeReport: function (collector, sync) {
198
var summarizer = new TreeSummarizer(),
199
outputFile = path.join(this.dir, this.file),
200
writer = this.opts.writer || new FileWriter(sync),
201
projectRoot = this.projectRoot,
202
that = this,
203
tree,
204
root;
205
206
collector.files().forEach(function (key) {
207
summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key)));
208
});
209
tree = summarizer.getTreeSummary();
210
root = tree.root;
211
writer.on('done', function () { that.emit('done'); });
212
writer.writeFile(outputFile, function (contentWriter) {
213
walk(root, collector, contentWriter, 0, projectRoot);
214
writer.done();
215
});
216
}
217
});
218
219
module.exports = CoberturaReport;
220
221