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 |
1 |
var Module = require('module'), |
6 |
|
path = require('path'), |
7 |
|
fs = require('fs'), |
8 |
|
nopt = require('nopt'), |
9 |
|
which = require('which'), |
10 |
|
mkdirp = require('mkdirp'), |
11 |
|
existsSync = fs.existsSync || path.existsSync, |
12 |
|
inputError = require('../../util/input-error'), |
13 |
|
matcherFor = require('../../util/file-matcher').matcherFor, |
14 |
|
Instrumenter = require('../../instrumenter'), |
15 |
|
Collector = require('../../collector'), |
16 |
|
formatOption = require('../../util/help-formatter').formatOption, |
17 |
|
hook = require('../../hook'), |
18 |
|
Report = require('../../report'), |
19 |
|
DEFAULT_REPORT_FORMAT = 'lcov'; |
20 |
|
|
21 |
1 |
function usage(arg0, command) { |
22 |
4 |
console.error('\nUsage: ' + arg0 + ' ' + command + ' [<options>] <executable-js-file-or-command> [-- <arguments-to-jsfile>]\n\nOptions are:\n\n' |
23 |
|
+ [ |
24 |
|
formatOption('--root <path> ', 'the root path to look for files to instrument, defaults to .'), |
25 |
|
formatOption('-x <exclude-pattern> [-x <exclude-pattern>]', 'one or more fileset patterns e.g. "**/vendor/**"'), |
26 |
|
formatOption('--[no-]default-excludes', 'apply default excludes [ **/node_modules/**, **/test/**, **/tests/** ], defaults to true'), |
27 |
|
formatOption('--report <report-type>', 'report type, one of html, lcov, lcovonly, none, defaults to lcov (= lcov.info + HTML)'), |
28 |
|
formatOption('--dir <report-dir>', 'report directory, defaults to ./coverage'), |
29 |
|
formatOption('--verbose, -v', 'verbose mode') |
30 |
|
].join('\n\n') + '\n'); |
31 |
4 |
console.error('\n'); |
32 |
|
} |
33 |
|
|
34 |
1 |
function run(args, commandName, enableHooks) { |
35 |
|
|
36 |
|
var config = { |
37 |
|
root: path, |
38 |
|
x: [Array, String], |
39 |
|
report: String, |
40 |
|
dir: path, |
41 |
|
verbose: Boolean, |
42 |
|
yui: Boolean, |
43 |
|
'default-excludes': Boolean |
44 |
|
}, |
45 |
|
opts = nopt(config, { v : '--verbose' }, args, 0), |
46 |
|
cmdAndArgs = opts.argv.remain, |
47 |
|
cmd, |
48 |
|
cmdArgs, |
49 |
|
reportingDir, |
50 |
|
reportClassName, |
51 |
|
report, |
52 |
|
runFn, |
53 |
|
excludes; |
54 |
|
|
55 |
|
if (cmdAndArgs.length === 0) { |
56 |
|
throw inputError.create('Need a filename argument for the ' + commandName + ' command!'); |
57 |
|
} |
58 |
|
|
59 |
|
cmd = cmdAndArgs.shift(); |
60 |
|
cmdArgs = cmdAndArgs; |
61 |
|
|
62 |
|
if (!existsSync(cmd)) { |
63 |
|
try { |
64 |
|
cmd = which.sync(cmd); |
65 |
|
} catch (ex) { |
66 |
|
throw inputError.create('Unable to resolve file [' + cmd + ']'); |
67 |
|
} |
68 |
|
} else { |
69 |
|
cmd = path.resolve(cmd); |
70 |
|
} |
71 |
|
|
72 |
|
runFn = function () { |
73 |
|
process.argv = ["node", cmd].concat(cmdArgs); |
74 |
|
if (opts.verbose) { |
75 |
|
console.log('Running: ' + process.argv.join(' ')); |
76 |
|
} |
77 |
|
Module.runMain(cmd, null, true); |
78 |
|
}; |
79 |
|
|
80 |
|
excludes = typeof opts['default-excludes'] === 'undefined' || opts['default-excludes'] ? |
81 |
|
[ '**/node_modules/**', '**/test/**', '**/tests/**' ] : []; |
82 |
|
excludes.push.apply(excludes, opts.x); |
83 |
|
|
84 |
|
if (enableHooks) { |
85 |
|
reportingDir = opts.dir || path.resolve(process.cwd(), 'coverage'); |
86 |
|
mkdirp.sync(reportingDir); |
87 |
|
reportClassName = opts.report || DEFAULT_REPORT_FORMAT; |
88 |
|
report = Report.create(reportClassName, { dir: reportingDir }); |
89 |
|
|
90 |
|
matcherFor({ |
91 |
|
root: opts.root || process.cwd(), |
92 |
|
includes: [ '**/*.js' ], |
93 |
|
excludes: excludes |
94 |
|
}, |
95 |
|
function (err, matchFn) { |
96 |
|
if (err) { throw err; } |
97 |
|
|
98 |
|
var coverageVar = '$$cov_' + new Date().getTime() + '$$', |
99 |
|
instrumenter = new Instrumenter({ coverageVariable: coverageVar }), |
100 |
|
transformer = instrumenter.instrumentSync.bind(instrumenter), |
101 |
|
hookOpts = { verbose: opts.verbose }; |
102 |
|
|
103 |
|
if (opts.yui) { //EXPERIMENTAL code: do not rely on this in anyway until the docs say it is allowed |
104 |
|
hookOpts.postLoadHook = require('../../util/yui-load-hook').getPostLoadHook(matchFn, transformer, opts.verbose); |
105 |
|
} |
106 |
|
hook.hookRequire(matchFn, transformer, hookOpts); |
107 |
|
process.once('exit', function () { |
108 |
|
var file = path.resolve(reportingDir, 'coverage.json'), |
109 |
|
collector, |
110 |
|
cov; |
111 |
|
if (typeof global[coverageVar] === 'undefined') { |
112 |
|
console.error('No coverage information was collected, exit without writing coverage information'); |
113 |
|
return; |
114 |
|
} else { |
115 |
|
cov = global[coverageVar]; |
116 |
|
} |
117 |
|
//important: there is no event loop at this point |
118 |
|
//everything that happens in this exit handler MUST be synchronous |
119 |
|
console.log('============================================================================='); |
120 |
|
console.log('Writing coverage object [' + file + ']'); |
121 |
|
fs.writeFileSync(file, JSON.stringify(cov), 'utf8'); |
122 |
|
collector = new Collector(); |
123 |
|
collector.add(cov); |
124 |
|
if (report) { |
125 |
|
console.log('Writing coverage reports at [' + reportingDir + ']'); |
126 |
|
report.writeReport(collector, true); |
127 |
|
} |
128 |
|
console.log('============================================================================='); |
129 |
|
}); |
130 |
|
runFn(); |
131 |
|
}); |
132 |
|
} else { |
133 |
|
runFn(); |
134 |
|
} |
135 |
|
} |
136 |
|
|
137 |
1 |
module.exports = { |
138 |
|
run: run, |
139 |
|
usage: usage |
140 |
|
}; |
141 |
|
|