| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- /*
- Copyright 2012-2015, Yahoo Inc.
- Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
- */
- const path = require('path');
- const fs = require('fs');
- const mkdirp = require('make-dir');
- const compareVersions = require('compare-versions');
- const libInstrument = require('istanbul-lib-instrument');
- const libCoverage = require('istanbul-lib-coverage');
- const libSourceMaps = require('istanbul-lib-source-maps');
- const hook = require('istanbul-lib-hook');
- const matcherFor = require('./file-matcher').matcherFor;
- const Reporter = require('./reporter');
- function getCoverFunctions(config, includes, callback) {
- if (!callback && typeof includes === 'function') {
- callback = includes;
- includes = null;
- }
- const includePid = config.instrumentation.includePid();
- const reportingDir = path.resolve(config.reporting.dir());
- const reporter = new Reporter(config);
- const excludes = config.instrumentation.excludes(true);
- // The coverage variable below should have different value than
- // that of the coverage variable actually used by the instrumenter (in this case: __coverage__).
- // Otherwise if you run nyc to provide coverage on these files,
- // both the actual instrumenter and this file will write to the global coverage variable,
- // and provide unexpected coverage result.
- const coverageVar = '$$coverage$$';
- const instOpts = config.instrumentation.getInstrumenterOpts();
- const sourceMapStore = libSourceMaps.createSourceMapStore({});
- let fakeRequire;
- instOpts.coverageVariable = coverageVar;
- instOpts.sourceMapUrlCallback = function(file, url) {
- sourceMapStore.registerURL(file, url);
- };
- const coverageFinderFn = function() {
- return global[coverageVar];
- };
- const instrumenter = libInstrument.createInstrumenter(instOpts);
- const transformer = function(code, options) {
- const filename =
- typeof options === 'string' ? options : options.filename;
- return instrumenter.instrumentSync(code, filename);
- };
- const runInContextTransformer = function(code, options) {
- return transformer(code, options);
- };
- const runInThisContextTransformer = function(code, options) {
- return transformer(code, options);
- };
- const requireTransformer = function(code, options) {
- let cov;
- const ret = transformer(code, options);
- const filename =
- typeof options === 'string' ? options : options.filename;
- if (fakeRequire) {
- cov = coverageFinderFn();
- cov[filename] = instrumenter.lastFileCoverage();
- return 'function x() {}';
- }
- return ret;
- };
- const coverageSetterFn = function(cov) {
- global[coverageVar] = cov;
- };
- const reportInitFn = function() {
- // set up reporter
- mkdirp.sync(reportingDir); //ensure we fail early if we cannot do this
- reporter.addAll(config.reporting.reports());
- if (config.reporting.print() !== 'none') {
- switch (config.reporting.print()) {
- case 'detail':
- reporter.add('text');
- break;
- case 'both':
- reporter.add('text');
- reporter.add('text-summary');
- break;
- default:
- reporter.add('text-summary');
- break;
- }
- }
- };
- let disabler;
- const hookFn = function(matchFn) {
- const hookOpts = {
- verbose: config.verbose,
- extensions: config.instrumentation.extensions(),
- coverageVariable: coverageVar
- };
- //initialize the global variable
- coverageSetterFn({});
- reportInitFn();
- if (config.hooks.hookRunInContext()) {
- hook.hookRunInContext(matchFn, runInContextTransformer, hookOpts);
- }
- if (config.hooks.hookRunInThisContext()) {
- hook.hookRunInThisContext(
- matchFn,
- runInThisContextTransformer,
- hookOpts
- );
- if (compareVersions(process.versions.node, '6.0.0') === -1) {
- disabler = hook.hookRequire(
- matchFn,
- requireTransformer,
- hookOpts
- );
- }
- } else {
- disabler = hook.hookRequire(matchFn, requireTransformer, hookOpts);
- }
- };
- const unhookFn = function(matchFn) {
- if (disabler) {
- disabler();
- }
- hook.unhookRunInThisContext();
- hook.unhookRunInContext();
- hook.unloadRequireCache(matchFn);
- };
- const beforeReportFn = function(matchFn, cov) {
- const pidExt = includePid ? '-' + process.pid : '';
- const file = path.resolve(
- reportingDir,
- 'coverage' + pidExt + '.raw.json'
- );
- let missingFiles;
- const finalCoverage = cov;
- if (config.instrumentation.includeAllSources()) {
- if (config.verbose) {
- console.error("Including all sources not require'd by tests");
- }
- missingFiles = [];
- // Files that are not touched by code ran by the test runner is manually instrumented, to
- // illustrate the missing coverage.
- matchFn.files.forEach(file => {
- if (!cov[file]) {
- missingFiles.push(file);
- }
- });
- fakeRequire = true;
- missingFiles.forEach(file => {
- try {
- require(file);
- } catch (ex) {
- console.error('Unable to post-instrument: ' + file);
- }
- });
- }
- if (Object.keys(finalCoverage).length > 0) {
- if (config.verbose) {
- console.error(
- '============================================================================='
- );
- console.error('Writing coverage object [' + file + ']');
- console.error(
- 'Writing coverage reports at [' + reportingDir + ']'
- );
- console.error(
- '============================================================================='
- );
- }
- fs.writeFileSync(file, JSON.stringify(finalCoverage), 'utf8');
- }
- return finalCoverage;
- };
- const exitFn = function(matchFn, reporterOpts) {
- let cov;
- cov = coverageFinderFn() || {};
- cov = beforeReportFn(matchFn, cov);
- coverageSetterFn(cov);
- if (
- !(cov && typeof cov === 'object') ||
- Object.keys(cov).length === 0
- ) {
- console.error(
- 'No coverage information was collected, exit without writing coverage information'
- );
- return;
- }
- const coverageMap = libCoverage.createCoverageMap(cov);
- const transformed = sourceMapStore.transformCoverage(coverageMap);
- reporterOpts.sourceFinder = transformed.sourceFinder;
- reporter.write(transformed.map, reporterOpts);
- sourceMapStore.dispose();
- };
- excludes.push(
- path.relative(process.cwd(), path.join(reportingDir, '**', '*'))
- );
- includes =
- includes ||
- config.instrumentation.extensions().map(ext => '**/*' + ext);
- const matchConfig = {
- root:
- config.instrumentation.root() ||
- /* istanbul ignore next: untestable */ process.cwd(),
- includes,
- excludes
- };
- matcherFor(matchConfig, (err, matchFn) => {
- /* istanbul ignore if: untestable */
- if (err) {
- return callback(err);
- }
- return callback(null, {
- coverageFn: coverageFinderFn,
- hookFn: hookFn.bind(null, matchFn),
- exitFn: exitFn.bind(null, matchFn, {}), // XXX: reporter opts
- unhookFn: unhookFn.bind(null, matchFn)
- });
- });
- }
- module.exports = {
- getCoverFunctions
- };
|