mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-02 18:11:04 +08:00
237 lines
8.0 KiB
JavaScript
237 lines
8.0 KiB
JavaScript
/*
|
|
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
|
|
};
|