jscoverage reporter

[86.71%] 261/301 40
[87.63%] 85/97 12

index.js

[86%] 77/89 12
[92%] 23/25 2
Line Hits Source
1 /*!
2 * jscoverage: index.js
3 * Authors : fish (https://github.com/fishbar)
4 * Create : 2014-04-03 15:20:13
5 * CopyRight 2014 (c) Fish And Other Contributors
6 *
7 */
8 1 require('coffee-script').register();
9 1 var debug = require('debug')('jscoverage');
10 1 var fs = require('xfs');
11 1 var path = require('path');
12 1 var argv = require('optimist').argv;
13 1 var patch = require('./lib/patch');
14 1 var rptUtil = require('./reporter/util');
15 1 var cmd = argv['$0'];
16 1 var MODE_MOCHA = false;
17 1 process.__MOCHA_PREPARED = false;
18 1 var FLAG_LOCK = false;
19 if (/mocha/.test(cmd)) {
20 1 MODE_MOCHA = true;
21 }
22
23 if (MODE_MOCHA) {
24 1 prepareMocha();
25 }
26 1 var COV_REPORT_NAME = argv.name || 'jscoverage reporter';
27 /**
28 * prepare env for mocha test
29 * @covignore
30 */
31 function prepareMocha() {
32 var covIgnore = argv.covignore;
33 var cwd = process.cwd();
34 var covlevel = argv.coverage;
35 if (process.__MOCHA_PREPARED) {
36 return;
37 }
38 process.__MOCHA_PREPARED = true;
39 if (covlevel) {
40 var tmp = covlevel.split(',');
41 covlevel = {
42 high: parseInt(tmp[0], 10) / 100,
43 middle: parseInt(tmp[1], 10) / 100,
44 low: parseInt(tmp[2], 10) / 100
45 };
46 } else {
47 covlevel = {
48 high: 0.9,
49 middle: 0.7,
50 low: 0.3
51 };
52 }
53 debug('covlevel', covlevel);
54 /**
55 * add after hook
56 * @return {[type]} [description]
57 */
58 var supportReporters = ['list', 'spec', 'tap'];
59 process.nextTick(function () {
60 try {
61 after(function () {
62 if (FLAG_LOCK) {
63 return;
64 }
65 FLAG_LOCK = true;
66 if (typeof _$jscoverage === 'undefined') {
67 return;
68 }
69 try {
70 if (argv.covout === 'none') {
71 return;
72 }
73 if (!argv.covout) {
74 var mochaR = argv.reporter || argv.R;
75 if (supportReporters.indexOf(mochaR) !== -1) {
76 argv.covout = mochaR;
77 } else {
78 argv.covout = 'list';
79 }
80 }
81 var reporter;
82 if (/^\w+$/.test(argv.covout)) {
83 reporter = require('./reporter/' + argv.covout);
84 } else {
85 reporter = require(argv.covout);
86 }
87 reporter.process(_$jscoverage, exports.coverageStats(), covlevel, COV_REPORT_NAME, rptUtil);
88 } catch (e) {
89 console.error('jscoverage reporter error', e, e.stack);
90 }
91 });
92 } catch (e) {
93 // do nothing
94 }
95 });
96 if (argv.covinject) {
97 debug('covinject enabled');
98 patch.enableInject(true);
99 }
100 if (!covIgnore) {
101 try {
102 var stat = fs.statSync('.covignore');
103 stat && (covIgnore = '.covignore');
104 debug('.covignore file found!');
105 } catch (e) {
106 return;
107 }
108 }
109 try {
110 covIgnore = fs.readFileSync(covIgnore).toString().split(/\r?\n/g);
111 debug('loading .covignore file!');
112 } catch (e) {
113 throw new Error('jscoverage loading covIgnore file error:' + covIgnore);
114 }
115 var _ignore = [];
116 covIgnore.forEach(function (v, i, a) {
117 if (!v) {
118 return;
119 }
120 if (v.indexOf('/') === 0) {
121 v = '^' + cwd + v;
122 }
123 _ignore.push(new RegExp(v.replace(/\./g, '\\.').replace(/\*/g, '.*')));
124 });
125
126 patch.setCovIgnore(_ignore);
127 }
128
129 1 var jscoverage = require('./lib/jscoverage');
130
131 /**
132 * enableInject description
133 * @param {Boolean} true or false
134 */
135 1 exports.enableInject = patch.enableInject;
136 /**
137 * config the inject function names
138 * @param {Object} obj {get, replace, call, reset}
139 * @example
140 *
141 * jsc.config({get:'$get', replace:'$replace'});
142 *
143 * =====================
144 *
145 * testMod = require('testmodule');
146 * testMod.$get('name');
147 * testMod.$replace('name', obj);
148 */
149 1 exports.config = function (obj) {
150 1 var inject_functions = patch.getInjectFunctions();
151 for (var i in obj) {
152 4 inject_functions[i] = obj[i];
153 }
154 };
155 /**
156 * process Code, inject the coverage code to the input Code string
157 * @param {String} filename jscoverage file flag
158 * @param {Code} content
159 * @return {Code} instrumented code
160 */
161 1 exports.process = jscoverage.process;
162
163 /**
164 * processFile, instrument singfile
165 * @sync
166 * @param {Path} source absolute Path
167 * @param {Path} dest absolute Path
168 * @param {Object} option [description]
169 */
170 1 exports.processFile = function (source, dest, option) {
171 6 var content;
172 6 var stats;
173 // test source is file or dir, or not a file
174 try {
175 6 stats = fs.statSync(source);
176 if (stats.isDirectory()) {
177 1 throw new Error('path is dir');
178 } else if (!stats.isFile()) {
179 1 throw new Error('path is not a regular file');
180 }
181 } catch (e) {
182 5 throw new Error('source file error' + e);
183 }
184
185 1 fs.sync().mkdir(path.dirname(dest));
186
187 1 content = fs.readFileSync(source).toString();
188 1 var sheBang = false;
189 if (content.charCodeAt(0) === 65279) {
190 0 content = content.substr(1);
191 }
192 // cut the shebang
193 if (content.indexOf('#!') === 0) {
194 0 var firstLineEnd = content.indexOf('\n');
195 0 sheBang = content.substr(0, firstLineEnd + 1);
196 0 content = content.substr(firstLineEnd + 1);
197 }
198 // check if coffee script
199 1 var ext = path.extname(source);
200 if (ext === '.coffee' || ext === '.litcoffee') {
201 0 var CoffeeScript = require('coffee-script');
202 0 content = CoffeeScript.compile(content, {
203 filename: source
204 });
205 }
206 1 content = this.process(source, content);
207 if (sheBang) {
208 0 content = sheBang + content;
209 }
210 1 fs.writeFileSync(dest, content);
211 };
212
213 function fixData(num) {
214 14 return Math.round(num * 10000) / 10000;
215 }
216 /**
217 * sum the coverage rate
218 * @public
219 */
220 1 exports.coverageStats = function () {
221 1 var file;
222 1 var tmp;
223 1 var lineTotal;
224 1 var lineHits;
225 1 var branchTotal;
226 1 var branchHits;
227 1 var n, len;
228 1 var stats = {};
229 1 var branches, branchesMap, branch;
230 1 var line, start, offset;
231 if (typeof _$jscoverage === 'undefined') {
232 0 return;
233 }
234 for (var i in _$jscoverage) {
235 7 file = i;
236 7 tmp = _$jscoverage[i];
237 if (!tmp.length) {
238 0 continue;
239 }
240 // reset the counters;
241 7 lineTotal = lineHits = 0;
242 7 branchTotal = branchHits = 0;
243
244 for (n = 0, len = tmp.length; n < len; n++) {
245 if (tmp[n] !== undefined) {
246 301 lineTotal ++;
247 if (tmp[n] > 0) {
248 261 lineHits ++;
249 }
250 }
251 }
252 // calculate the branches coverage
253 7 branches = tmp.condition;
254 7 branchesMap = {};
255 for (n in branches) {
256 if (branches[n] === 0) {
257 12 branch = n.split('_');
258 12 line = branch[0];
259 12 start = parseInt(branch[1], 10);
260 12 offset = parseInt(branch[2], 10);
261 if (!branchesMap[line]) {
262 11 branchesMap[line] = [];
263 }
264 12 branchesMap[line].push([start, offset]);
265 } else {
266 85 branchHits ++;
267 }
268 97 branchTotal ++;
269 }
270 7 stats[file] = {
271 lineSloc: lineTotal,
272 lineHits: lineHits,
273 lineCoverage: lineTotal ? fixData(lineHits / lineTotal) : 0,
274 branchSloc: branchTotal,
275 branchHits: branchHits,
276 branchCoverage: branchTotal ? fixData(branchHits / branchTotal) : 0,
277 branches: branchesMap
278 };
279 }
280 1 return stats;
281 };
282
283 /**
284 * get lcov report
285 * @return {[type]} [description]
286 */
287 1 exports.getLCOV = function () {
288 1 var tmp;
289 1 var total;
290 1 var touched;
291 1 var n, len;
292 1 var lcov = '';
293 if (typeof _$jscoverage === 'undefined') {
294 0 return;
295 }
296 1 Object.keys(_$jscoverage).forEach(function (file) {
297 6 lcov += 'SF:' + file + '\n';
298 6 tmp = _$jscoverage[file];
299 if (!tmp.length) {
300 0 return;
301 }
302 6 total = touched = 0;
303 for (n = 0, len = tmp.length; n < len; n++) {
304 if (tmp[n] !== undefined) {
305 249 lcov += 'DA:' + n + ',' + tmp[n] + '\n';
306 249 total ++;
307 if (tmp[n] > 0) {
308 173 touched++;
309 }
310 }
311 }
312 6 lcov += 'end_of_record\n';
313 });
314 1 return lcov;
315 };
316

lib/instrument.js

[90%] 57/63 6
[87%] 36/41 5
Line Hits Source
1 /*!
2 * jscoverage: lib/instrument.js
3 * Authors : fish (https://github.com/fishbar)
4 * Create : 2014-04-03 15:20:13
5 * CopyRight 2014 (c) Fish And Other Contributors
6 */
7 /**
8 * instrument code
9 * @example
10 * var ist = new Instrument();
11 * var resCode = ist.process(str);
12 */
13 1 var debug = require('debug')('cov:instrument');
14 1 var Uglify = require('uglify-js');
15
16 function Instrument() {
17 /**
18 * filename needed
19 * @type {String}
20 */
21 1 this.filename = null;
22 /**
23 * store injected code
24 * @type {String}
25 */
26 1 this.code = null;
27 /**
28 * 储存line信息
29 * @type {Array}
30 */
31 1 this.lines = [];
32 /**
33 * 储存condition信息
34 * @type {Object}
35 */
36 1 this.conds = {};
37 /**
38 * source code in array
39 * @type {Array}
40 */
41 1 this.source = null;
42 }
43
44 1 Instrument.prototype = {
45 // 行类型
46 T_LINE: 'line',
47 T_COND: 'cond',
48 /**
49 * process code
50 * @public
51 * @param {String} code source code
52 * @return {String} injected code
53 */
54 process: function (filename, code) {
55 if (!filename) {
56 0 throw new Error('[jscoverage]instrument need filename!');
57 }
58
59 1 var ist = this;
60 // parse ast
61 1 var ast = Uglify.parse(code);
62
63 1 this.filename = filename;
64 1 this.source = code.split(/\r?\n/);
65
66 // init walker
67 1 var walker = new Uglify.TreeWalker(function (node) {
68 if (ist.checkIfIgnore(node, walker.stack)) {
69 4 return;
70 }
71 263 var parent = this.parent();
72 if (node instanceof Uglify.AST_Conditional) { // 三元判断
73 1 debug('node type:', node.TYPE, 'parent type:', parent && parent.TYPE);
74 1 node.consequent = ist.inject('cond', node.consequent.start.line, node.consequent);
75 1 node.alternative = ist.inject('cond', node.alternative.start.line, node.alternative);
76 } else if (node.TYPE === 'Binary') {
77 3 debug('node type:', node.TYPE, 'parent type:', parent && parent.TYPE);
78 if (node.operator && ['||', '&&'].indexOf(node.operator) === -1) {
79 3 return;
80 }
81 if (!(node.left instanceof Uglify.AST_Constant)) {
82 0 node.left = ist.inject('cond', node.left.start.line, node.left);
83 }
84 if (!(node.right instanceof Uglify.AST_Constant)) {
85 0 node.right = ist.inject('cond', node.right.start.line, node.right);
86 }
87 }
88
89 else if (node instanceof Uglify.AST_If) {
90 0 debug('node type:', node.TYPE, 'parent type:', parent && parent.TYPE);
91 0 node.condition = ist.inject('cond', node.condition.start.line, node.condition);
92 }
93
94 260 var len = node.body ? node.body.length : 0;
95 if (len) {
96 7 var res = [];
97 7 var subNode;
98 for (var i = 0; i < len; i++) {
99 30 subNode = node.body[i];
100 if (ist.checkIfIgnore(subNode, walker.stack)) {
101 1 res.push(subNode);
102 1 continue;
103 }
104 if (subNode instanceof Uglify.AST_Statement) {
105 if (ist.ifExclude(subNode)) {
106 6 res.push(subNode);
107 6 continue;
108 }
109 23 res.push(ist.inject('line', subNode.start.line));
110 } else if (subNode instanceof Uglify.AST_Var) {
111 0 res.push(ist.inject('line', subNode.start.line));
112 }
113 23 res.push(subNode);
114 }
115 7 node.body = res;
116 }
117 });
118 // figure_out_scope
119 1 ast.figure_out_scope();
120 // walk process
121 1 ast.walk(walker);
122
123 1 var out = Uglify.OutputStream({
124 preserve_line : true,
125 comments: 'all',
126 beautify: true
127 });
128 // rebuild file
129 1 ast.print(out);
130 1 this.code = out.toString();
131 1 return this;
132 },
133 /**
134 * 注入覆盖率查询方法
135 * @private
136 * @param {String} type inject type, line | conds
137 * @param {Number} line line number
138 * @param {Object} expr any expression, or node, or statement
139 * @return {AST_Func} Object
140 */
141 inject: function (type, line, expr) {
142 25 var args = [];
143 if (type === this.T_LINE) {
144 23 this.lines.push(line);
145 23 args = [
146 new Uglify.AST_String({value: this.filename}),
147 new Uglify.AST_String({value: type}),
148 new Uglify.AST_Number({value: line})
149 ];
150 } else if (type === this.T_COND) {
151 2 var start = expr.start.col;
152 2 var offset = expr.end.endpos - expr.start.pos;
153 2 var key = line + '_' + start + '_' + offset; // 编码
154 2 this.conds[key] = 0;
155 2 args = [
156 new Uglify.AST_String({value: this.filename}),
157 new Uglify.AST_String({value: type}),
158 new Uglify.AST_String({value: key}),
159 expr
160 ];
161 }
162
163 25 var call = new Uglify.AST_Call({
164 expression: new Uglify.AST_SymbolRef({name: '_$jscmd'}),
165 //end: new Uglify.AST_
166 args: args
167 });
168
169 if (type === this.T_LINE) {
170 23 return new Uglify.AST_SimpleStatement({
171 body: call,
172 end: new Uglify.AST_Token({value: ';'})
173 });
174 } else {
175 2 return call;
176 }
177 },
178 /**
179 * check if need inject
180 * @param {AST_Node} node
181 * @return {Boolean}
182 */
183 ifExclude: function (node) {
184 if (node instanceof Uglify.AST_LoopControl) {
185 2 return false;
186 }
187 if (
188 node instanceof Uglify.AST_IterationStatement ||
189 node instanceof Uglify.AST_StatementWithBody ||
190 node instanceof Uglify.AST_Block
191 ) {
192 6 return true;
193 }
194 },
195 checkIfIgnore: function (node, stack) {
196 297 var cmt;
197 if (node.start && node.start.comments_before.length) {
198 16 cmt = node.start.comments_before[node.start.comments_before.length - 1];
199 if (/@covignore/.test(cmt.value) && !(node instanceof Uglify.AST_Toplevel)) {
200 4 node.__covignore = true;
201 }
202 }
203 if (node.__covignore) {
204 4 return true;
205 }
206 if (stack) {
207 for (var i = stack.length - 1; i > 0; i--) {
208 if (stack[i].__covignore) {
209 1 return true;
210 }
211 }
212 }
213 292 return false;
214 }
215 };
216
217 1 module.exports = Instrument;

lib/jscoverage.js

[93%] 28/30 2
[100%] 4/4 0
Line Hits Source
1 /*!
2 * jscoverage: lib/jscoverage.js
3 * Authors : fish (https://github.com/fishbar)
4 * Create : 2014-04-03 15:20:13
5 * CopyRight 2014 (c) Fish And Other Contributors
6 */
7 1 var Instrument = require('./instrument');
8
9 /**
10 * do not exec this function
11 * the function body will insert into instrument files
12 *
13 * _$jscoverage = {
14 * filename : {
15 * line1: 0
16 * line2: 1
17 * line3: undefined
18 * ....
19 * source: [],
20 * condition: [
21 * line_start_offset
22 * ]
23 * }
24 * }
25 * @covignore
26 */
27 function jscFunctionBody() {
28 // instrument by jscoverage, do not modifly this file
29 (function (file, lines, conds, source) {
30 var BASE;
31 if (typeof global === 'object') {
32 BASE = global;
33 } else if (typeof window === 'object') {
34 BASE = window;
35 } else {
36 throw new Error('[jscoverage] unknow ENV!');
37 }
38 if (BASE._$jscoverage) {
39 BASE._$jscmd(file, 'init', lines, conds, source);
40 return;
41 }
42 var cov = {};
43 /**
44 * jsc(file, 'init', lines, condtions)
45 * jsc(file, 'line', lineNum)
46 * jsc(file, 'cond', lineNum, expr, start, offset)
47 */
48 function jscmd(file, type, line, express, start, offset) {
49 var storage;
50 switch (type) {
51 case 'init':
52 if(cov[file]){
53 storage = cov[file];
54 } else {
55 storage = [];
56 for (var i = 0; i < line.length; i ++) {
57 storage[line[i]] = 0;
58 }
59 var condition = express;
60 var source = start;
61 storage.condition = condition;
62 storage.source = source;
63 }
64 cov[file] = storage;
65 break;
66 case 'line':
67 storage = cov[file];
68 storage[line] ++;
69 break;
70 case 'cond':
71 storage = cov[file];
72 storage.condition[line] ++;
73 return express;
74 }
75 }
76
77 BASE._$jscoverage = cov;
78 BASE._$jscmd = jscmd;
79 jscmd(file, 'init', lines, conds, source);
80 })('$file$', $lines$, $conds$, $source$);
81 }
82 /**
83 * gen coverage head
84 */
85 function genCodeCoverage(instrObj) {
86 if (!instrObj) {
87 0 return '';
88 }
89 1 var code = [];
90 1 var filename = instrObj.filename;
91 // Fix windows path
92 1 filename = filename.replace(/\\/g, '/');
93 1 var lines = instrObj.lines;
94 1 var conditions = instrObj.conds;
95 1 var src = instrObj.source;
96 1 var jscfArray = jscFunctionBody.toString().split('\n');
97 1 jscfArray = jscfArray.slice(1, jscfArray.length - 1);
98 1 var ff = jscfArray.join('\n').replace(/(^|\n) {2}/g, '\n')
99 .replace(/\$(\w+)\$/g, function (m0, m1){
100 switch (m1) {
101 case 'file':
102 1 return filename;
103 case 'lines':
104 1 return JSON.stringify(lines);
105 case 'conds':
106 1 return JSON.stringify(conditions);
107 case 'source':
108 1 return JSON.stringify(src);
109 }
110 });
111 1 code.push(ff);
112 1 code.push(instrObj.code);
113 1 return code.join('\n');
114 }
115
116 1 exports.process = function (filename, content) {
117 if (!filename) {
118 1 throw new Error('jscoverage.process(filename, content), filename needed!');
119 }
120 2 filename = filename.replace(/\\/g, '/');
121 if (!content) {
122 1 return '';
123 }
124 1 var pwd = process.cwd();
125 1 var fname;
126 if (filename.indexOf(pwd) === 0) {
127 1 fname = filename.substr(pwd.length + 1);
128 } else {
129 0 fname = filename;
130 }
131 1 var instrObj;
132 1 var ist = new Instrument();
133 1 instrObj = ist.process(fname, content);
134 1 return genCodeCoverage(instrObj);
135 };
136

lib/patch.js

[100%] 20/20 0
[100%] 2/2 0
Line Hits Source
1 /*!
2 * jscoverage: lib/patch.js
3 * Authors : fish (https://github.com/fishbar)
4 * Create : 2014-04-03 15:20:13
5 * CopyRight 2014 (c) Fish And Other Contributors
6 */
7 1 var Module = require('module');
8 1 var path = require('path');
9 1 var fs = require('fs');
10 1 var argv = require('optimist').argv;
11 1 var jscoverage = require('./jscoverage');
12
13 1 var covInject = false;
14 1 var defaultCovIgnore = [
15 new RegExp('^' + process.cwd() + '/node_modules/'),
16 new RegExp('^' + process.cwd() + '/test/')
17 ];
18 1 var covIgnore = defaultCovIgnore;
19
20 1 var injectFunctions = {
21 get : '_get',
22 replace : '_replace',
23 call : '_call',
24 reset : '_reset',
25 test: '_test'
26 };
27
28 1 exports.getInjectFunctions = function () {
29 1 return injectFunctions;
30 };
31
32 1 exports.enableInject = function (bool) {
33 1 covInject = bool;
34 };
35 1 exports.setCovIgnore = function (ignore) {
36 2 covIgnore = ignore.concat(defaultCovIgnore);
37 };
38 /**
39 * do mock things here
40 * @covignore
41 */
42 (function () {
43 if (Module.prototype.__jsc_patch__) {
44 return;
45 }
46 Module.prototype.__jsc_patch__ = true;
47 var origin_require = Module.prototype.require;
48 var processExts = ['.js', '.coffee', '.litcoffee'];
49 Module.prototype.require = function (filename) {
50 var needinject = covInject;
51 var ff = filename;
52 filename = Module._resolveFilename(filename, this);
53 var ext = path.extname(filename);
54 var flagjsc = checkModule(filename);
55 if (typeof filename === 'object') {
56 filename = filename[0];
57 }
58 if (!flagjsc || processExts.indexOf(ext) === -1) {
59 return origin_require.call(this, filename);
60 }
61
62 var cachedModule = Module._cache[filename];
63 // take care of module cache
64 if (flagjsc && cachedModule && cachedModule.__coveraged__) {
65 return cachedModule.exports;
66 }
67 // console.log('jscoverage:', ff, 'cov', flagjsc, 'inject', needinject);
68 var module = new Module(filename, this);
69 try {
70 module.filename = filename;
71 module.paths = Module._nodeModulePaths(path.dirname(filename));
72 Module._extensions[ext](module, filename, {
73 flagjsc : flagjsc,
74 needinject : needinject
75 });
76 module.__coveraged__ = flagjsc;
77 module.loaded = true;
78 Module._cache[filename] = module;
79 } catch (err) {
80 delete Module._cache[filename];
81 console.error(filename, err.stack);
82 throw err;
83 }
84 return module.exports;
85 };
86 function stripBOM(content) {
87 // Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
88 // because the buffer-to-string conversion in `fs.readFileSync()`
89 // translates it to FEFF, the UTF-16 BOM.
90 if (content.charCodeAt(0) === 0xFEFF) {
91 content = content.slice(1);
92 }
93 return content;
94 }
95 Module._extensions['.js'] = function (module, filename, status) {
96 var content = fs.readFileSync(filename, 'utf8');
97 var tmpFuncBody;
98 var injectFn = exports.getInjectFunctions();
99 // trim first line when script is a shell script
100 // content = content.replace(/^\#\![^\n]+\n/, '');
101 if (status && status.flagjsc) {
102 content = jscoverage.process(filename, content);
103 }
104 if (status && status.needinject) {
105 tmpFuncBody = injectFunctionBody.toString().replace(/\$\$(\w+)\$\$/g, function (m0, m1) {
106 return injectFunctions[m1];
107 });
108 tmpFuncBody = tmpFuncBody.split(/\n/);
109 content += '\n' + tmpFuncBody.slice(1, tmpFuncBody.length - 1).join('\n');
110 }
111 module._compile(stripBOM(content), filename);
112 };
113 if (Module._extensions['.coffee']) {
114 Module._extensions['.coffee'] = Module._extensions['.litcoffee'] = function (module, filename, status) {
115 var CoffeeScript = require('coffee-script');
116 var content = CoffeeScript._compileFile(filename, false);
117 var tmpFuncBody;
118 var injectFn = exports.getInjectFunctions();
119 // trim first line when script is a shell script
120 // content = content.replace(/^\#\![^\n]+\n/, '');
121 if (status && status.flagjsc) {
122 content = jscoverage.process(filename, content);
123 }
124 if (status && status.needinject) {
125 tmpFuncBody = injectFunctionBody.toString().replace(/\$\$(\w+)\$\$/g, function (m0, m1) {
126 return injectFunctions[m1];
127 });
128 tmpFuncBody = tmpFuncBody.split(/\n/);
129 content += '\n' + tmpFuncBody.slice(1, tmpFuncBody.length - 1).join('\n');
130 }
131 module._compile(stripBOM(content), filename);
132 };
133 }
134 })();
135
136 function checkModule(module) {
137
138 // native module
139 if (!/\//.test(module)) {
140 2 return false;
141 }
142 // modules in node_modules
143 3 var flagIgnore = false;
144 3 covIgnore.forEach(function (v) {
145 if (v.test(module)) {
146 2 flagIgnore = true;
147 }
148 });
149 3 return !flagIgnore;
150 }
151
152 /**
153 * do not exec this function
154 * @covignore
155 */
156 function injectFunctionBody() {
157 (function (){
158 if (module.exports._i_n_j_e_c_t_e_d_) {
159 return;
160 }
161 if (module.exports.$$call$$ || module.exports.$$get$$ ||
162 module.exports.$$replace$$ || module.exports.$$reset$$) {
163 throw new Error("[jscoverage] jscoverage can not inject function for this module, because the function is exists! using jsc.config({inject:{}})");
164 }
165
166 var __r_e_p_l_a_c_e__ = {};
167 module.exports.$$replace$$ = function (name, obj) {
168 function stringify(obj) {
169 if (obj === null) {
170 return 'null';
171 }
172 if (obj === undefined){
173 return 'undefined';
174 }
175 if (!obj && isNaN(obj)){
176 return 'NaN';
177 }
178 if (typeof obj === 'string') {
179 return '"' + obj.replace(/"/g, '\\"') + '"';
180 }
181 if (typeof obj === 'number') {
182 return obj;
183 }
184 if (obj.constructor === Date) {
185 return 'new Date(' + obj.getTime() + ')';
186 }
187 if (obj.constructor === Function) {
188 return obj.toString();
189 }
190 if (obj.constructor === RegExp) {
191 return obj.toString();
192 }
193 var is_array = obj.constructor === Array ? true : false;
194 var res, i;
195 if (is_array) {
196 res = ['['];
197 for (i = 0; i < obj.length; i++) {
198 res.push(stringify(obj[i]));
199 res.push(',');
200 }
201 if (res[res.length - 1] === ',') {
202 res.pop();
203 }
204 res.push(']');
205 } else {
206 res = ['{'];
207 for (i in obj) {
208 res.push(i + ':' + stringify(obj[i]));
209 res.push(',');
210 }
211 if (res[res.length - 1] === ',')
212 res.pop();
213 res.push('}');
214 }
215 return res.join('');
216 }
217 if (!__r_e_p_l_a_c_e__.hasOwnProperty(name)) {
218 __r_e_p_l_a_c_e__[name] = eval(name);
219 }
220 eval(name + "=" + stringify(obj));
221 };
222 module.exports.$$reset$$ = function (name) {
223 var script;
224 if (name) {
225 script = 'if(__r_e_p_l_a_c_e__.hasOwnProperty("' + name + '"))' + name + ' = __r_e_p_l_a_c_e__["' + name + '"];';
226 } else {
227 script = 'for(var i in __r_e_p_l_a_c_e__){eval( i + " = __r_e_p_l_a_c_e__[\'" + i + "\'];");}';
228 }
229 eval(script);
230 };
231 module.exports.$$call$$ = module.exports.$$test$$ = function (func, args) {
232 var f, o;
233 if (func.match(/\\./)) {
234 func = func.split(".");
235 f = func[func.length - 1];
236 func.pop();
237 o = func.join(".");
238 } else {
239 f = func;
240 o = "this";
241 }
242 return eval(f + ".apply(" + o + "," + JSON.stringify(args) + ")");
243 };
244 module.exports.$$get$$ = function (objstr) {
245 return eval(objstr);
246 };
247 module.exports._i_n_j_e_c_t_e_d_ = true;
248 })();
249 }
250

reporter/html.js

[96%] 50/52 2
[100%] 18/18 0
Line Hits Source
1 /*!
2 * jscoverage: reporter/html.js
3 * Authors : fish (https://github.com/fishbar)
4 * Create : 2014-04-10 16:23:23
5 * CopyRight 2014 (c) Fish And Other Contributors
6 */
7
8 /**
9 * Module dependencies.
10 */
11
12 1 var fs = require('fs');
13 1 var ejs = require('ejs');
14 1 var path = require('path');
15
16 /**
17 * Initialize a new `JsCoverage` reporter.
18 *
19 * @param {Runner} runner
20 * @param {Boolean} output
21 * @api public
22 */
23
24 1 exports.process = function (_$jscoverage, stats, covlevel, name, utils) {
25 2 var result = map(_$jscoverage, stats);
26 1 var file = __dirname + '/templates/coverage.ejs';
27 1 var str = fs.readFileSync(file, 'utf8').toString();
28 1 result.instrumentation = name;
29 1 var html = ejs.render(str, {
30 debug: false,
31 cov: result,
32 coverageClass: function (n) {
33 // n = n;
34 if (n >= covlevel.high) {
35 16 return 'high';
36 }
37 if (n >= covlevel.middle) {
38 1 return 'medium';
39 }
40 if (n >= covlevel.low) {
41 5 return 'low';
42 }
43 8 return 'terrible';
44 },
45 formatCoverage:utils.formatCoverage,
46 filename: path.join(__dirname, './templates/cached.ejs')
47 });
48
49 1 fs.writeFileSync(process.cwd() + '/covreporter.html', html);
50 1 console.log('[REPORTER]: ', process.cwd() + '/covreporter.html');
51 };
52
53 /**
54 * Map jscoverage data to a JSON structure
55 * suitable for reporting.
56 *
57 * @param {Object} cov
58 * @return {Object}
59 * @api private
60 */
61
62 function map(cov, stats) {
63 2 var ret = {
64 lineSloc: 0,
65 lineHits: 0,
66 lineMisses: 0,
67 lineCoverage: 0,
68 branchSloc: 0,
69 branchHits: 0,
70 branchMisses: 0,
71 branchCoverage: 0,
72 files: []
73 };
74
75 for (var filename in cov) {
76 if (!cov[filename] || !cov[filename].length) {
77 0 continue;
78 }
79 14 var data = coverage(filename, cov[filename], stats[filename]);
80 13 ret.files.push(data);
81 13 ret.lineHits += data.lineHits;
82 13 ret.lineMisses += data.lineMisses;
83 13 ret.lineSloc += data.lineSloc;
84 13 ret.branchHits += data.branchHits;
85 13 ret.branchMisses += data.branchMisses;
86 13 ret.branchSloc += data.branchSloc;
87 }
88
89 1 ret.files.sort(function(a, b) {
90 12 return a.filename.localeCompare(b.filename);
91 });
92
93 if (ret.lineSloc > 0) {
94 1 ret.lineCoverage = Math.round(ret.lineHits / ret.lineSloc * 10000) / 100;
95 }
96 if (ret.branchSloc > 0) {
97 1 ret.branchCoverage = Math.round(ret.branchHits / ret.branchSloc * 10000) / 100;
98 }
99 1 return ret;
100 }
101
102 /**
103 * Map jscoverage data for a single source file
104 * to a JSON structure suitable for reporting.
105 *
106 * @param {String} filename name of the source file
107 * @param {Object} data jscoverage coverage data
108 * @return {Object}
109 * @api private
110 */
111
112 function coverage(filename, data, stats) {
113 14 var ret = {
114 filename: filename,
115 lineCoverage: stats.lineCoverage,
116 lineHits: stats.lineHits,
117 lineMisses: stats.lineSloc - stats.lineHits,
118 lineSloc: stats.lineSloc,
119 branchCoverage: stats.branchCoverage,
120 branchHits: stats.branchHits,
121 branchMisses: stats.branchSloc - stats.branchHits,
122 branchSloc: stats.branchSloc,
123 source: []
124 };
125 14 data.source.forEach(function(line, num){
126 2328 num++;
127 2329 var branches = stats.branches[num];
128 2330 var splits = [];
129 if (branches) {
130 40 branches.forEach(function (v) {
131 if (!splits[v[0]]) {
132 49 splits[v[0]] = {start:[], end:[]};
133 }
134 if (!splits[v[0] + v[1]]) {
135 49 splits[v[0] + v[1]] = {start: [], end: []};
136 }
137 50 splits[v[0]].start.push('');
138 50 splits[v[0] + v[1]].end.push('');
139 });
140 40 var res = [];
141 40 var offset = 0;
142 40 splits.forEach(function (v, i) {
143 if (!v) {
144 0 return;
145 }
146 98 res.push(line.substr(offset, i - offset));
147 98 res.push(v.end.join(''));
148 98 res.push(v.start.join(''));
149 98 offset = i;
150 });
151 40 res.push(line.substr(offset));
152 40 line = res.join('');
153 }
154 2356 ret.source[num] = {
155 source: line,
156 coverage: data[num] === undefined ? '' : data[num],
157 branches: branches && branches.length ? true : false
158 };
159 });
160 13 return ret;
161 }
162

reporter/util.js

[25%] 6/24 18
[20%] 1/5 4
Line Hits Source
1 1 exports.formatCoverage = function (n) {
2 14 var str = Math.floor(n * 100) + '%';
3 14 return str;
4 };
5
6 1 var symbols = {
7 ok: '✓',
8 warn: '⁍',
9 err: '✱'
10 };
11 // With node.js on Windows: use symbols available in terminal default fonts
12 if ('win32' === process.platform) {
13 0 symbols.ok = '\u221A';
14 0 symbols.warn = '\u204D';
15 0 symbols.err = '\u2731';
16 }
17
18 1 exports.getType = function (covlevel, coverage) {
19 0 var type;
20 0 var head;
21 if (coverage >= covlevel.high) {
22 0 type = 'GREEN';
23 0 head = symbols.ok;
24 } else if (coverage >= covlevel.middle) {
25 0 type = null;
26 0 head = symbols.ok;
27 } else if (coverage >= covlevel.low) {
28 0 type = 'YELLOW';
29 0 head = symbols.warn;
30 } else {
31 0 type = 'RED';
32 0 head = symbols.err;
33 }
34 0 return [type, head];
35 };
36 1 exports.colorful = function (str, type) {
37 if (!type) {
38 0 return str;
39 }
40 0 var head = '\x1B[', foot = '\x1B[0m';
41 0 var color = {
42 LINENUM : 36,
43 GREEN : 32,
44 YELLOW : 33,
45 RED : 31,
46 DEFAULT: 0
47 };
48 0 return head + color[type] + 'm' + str + foot;
49 };

test/abc.js

[100%] 23/23 0
[50%] 1/2 1
Line Hits Source
1 /*!
2 * jscoverage: test/abc.js
3 * Authors : fish (https://github.com/fishbar)
4 * Create : 2014-04-10 16:23:23
5 * CopyRight 2014 (c) Fish And Other Contributors
6 */
7 2 var cde = require('./cde');
8 2 var a = 1;
9 2 var b = 2;
10 2 var c = 3;
11 2 var d;
12 2 var e = a > 1 ? 1 : 2;
13
14 2 var reset = {
15 abc:function () {}
16 };
17
18 function abc() {
19 2 var tmp = a + b;
20 2 var t = 1;
21 // test require ok
22 2 cde.a();
23 // test switch coverage
24 2 testSwitch('first');
25 /* @covignore */
26 testSwitch('second');
27 2 testSwitch();
28 2 return tmp + c;
29 }
30
31 function testSwitch(act) {
32 6 var res = [
33 'a',
34 'b',
35 'c'
36 ];
37 6 var tmp;
38 switch (act) {
39 case 'first' :
40 2 tmp = res[0];
41 2 break;
42 case 'second' :
43 2 tmp = res[1];
44 2 break;
45 default:
46 2 tmp = res.join(',');
47 }
48 6 return tmp;
49 }
50 2 abc();
51 2 exports.abc = abc;
52