UNPKG

monocart-coverage-reports

Version:

A code coverage tool to generate native V8 reports or Istanbul reports.

281 lines (223 loc) 7.83 kB
const fs = require('fs'); const path = require('path'); const { pathToFileURL } = require('url'); const EC = require('eight-colors'); const defaultOptions = require('./default/options.js'); const Util = require('./utils/util.js'); const { initV8ListAndSourcemap } = require('./v8/v8.js'); const { getInputData, readFromDir, generateCoverageReports } = require('./generate.js'); const { getSnapshot, diffSnapshot } = require('./utils/snapshot.js'); const WSSession = require('./client/ws-session.js'); const CDPClient = require('./client/cdp-client.js'); const resolveConfigOptions = async (configPath) => { // json format const ext = path.extname(configPath); if (ext === '.json' || configPath.slice(-2) === 'rc') { return JSON.parse(Util.readFileSync(configPath)); } let configOptions; let err; try { configOptions = await import(pathToFileURL(configPath)); } catch (ee) { err = ee; } if (err) { Util.logError(`ERROR: failed to load config "${configPath}": ${err && err.message} `); return; } // could be multiple level default while (configOptions && configOptions.default) { configOptions = configOptions.default; } return configOptions; }; class CoverageReport { constructor(options = {}) { this.cacheDirName = '.cache'; this.constructorOptions = options; this.options = { ... defaultOptions, ... options }; this.initOptions(); this.fileCache = new Map(); } initOptions(force) { if (this.fixedOptions && !force) { return; } // before clean cache, do no call initOptions again in hasCache this.fixedOptions = true; // init logging for clean after generated this.logging = Util.initLoggingLevel(this.options.logging); if (Util.isNum(this.options.gc)) { Util.setGC(this.options.gc); } // init outputDir const outputDir = `${this.options.outputDir || defaultOptions.outputDir}`; this.options.outputDir = outputDir; // cache dir, it is useful if runs in multiple processes. // cache coverage- data and source- files // it will be removed after generated if logging it not debug. const cacheDir = path.resolve(outputDir, this.cacheDirName); this.options.cacheDir = cacheDir; // If true, the API `cleanCache()` will execute automatically. if (this.options.cleanCache) { this.cleanCache(); } } // add coverage data: {array} V8 format, {object} Istanbul format async add(data) { const time_start = Date.now(); this.initOptions(); if (!Util.checkCoverageData(data)) { Util.logError(`${this.options.name}: The added coverage data must be Array(V8) or Object(Istanbul)`); return; } const dataId = Util.uid(); const results = { id: dataId }; if (Array.isArray(data)) { results.type = 'v8'; // could be empty list after entryFilter results.data = await initV8ListAndSourcemap(this, data); } else { results.type = 'istanbul'; results.data = data; } // save data to cache const { cacheName, cachePath } = Util.getCacheFileInfo('coverage', dataId, this.options.cacheDir); this.fileCache.set(cacheName, results); await Util.writeFile(cachePath, JSON.stringify(results)); Util.logTime(`added coverage data: ${results.type}`, time_start); return results; } // add coverage from dir async addFromDir(dir) { const time_start = Date.now(); await readFromDir(this, dir); Util.logTime(`added from dir: ${dir}`, time_start); } // generate report async generate() { const time_start = Date.now(); this.initOptions(); const dataDir = this.options.dataDir; if (dataDir) { await this.addFromDir(dataDir); } const inputData = await getInputData(this); if (!inputData) { return; } // GC, no OOM this.fileCache.clear(); const { dataList, sourceCache } = inputData; // empty output dir except cache dir before generate reports const outputDir = this.options.outputDir; if (fs.existsSync(outputDir)) { // if assets dir is out of output dir will be ignore if (this.options.clean) { this.clean(); } } else { fs.mkdirSync(outputDir, { recursive: true }); } Util.logTime('┌ [generate] prepared coverage data', time_start); const coverageResults = await generateCoverageReports(dataList, sourceCache, this.options); const onEnd = this.options.onEnd; if (typeof onEnd === 'function') { await onEnd.call(this, coverageResults); } // remove cache dir after finished if (!Util.isDebug()) { Util.rmSync(this.options.cacheDir); } let reportPath = ''; if (coverageResults.reportPath) { reportPath = EC.cyan(coverageResults.reportPath); } Util.logTime(`generated coverage reports: ${reportPath}`, time_start); return coverageResults; } // check if cache dir exists hasCache() { this.initOptions(); return fs.existsSync(this.options.cacheDir); } // cache clean dir cleanCache() { // clean previous artifacts if (this.hasCache()) { Util.rmSync(this.options.cacheDir); return true; } return false; } // clean previous reports except cache dir and v8 coverage dir clean() { const outputDir = this.options.outputDir; let v8DirName; const nodeV8CoverageDir = process.env.NODE_V8_COVERAGE; if (nodeV8CoverageDir) { v8DirName = path.relative(outputDir, nodeV8CoverageDir); } fs.readdirSync(outputDir).forEach((itemName) => { if (itemName === this.cacheDirName) { return; } if (itemName === v8DirName) { return; } Util.rmSync(path.resolve(outputDir, itemName)); }); } // get entry filter handler getEntryFilter() { return Util.getEntryFilter(this.options); } // load config file async loadConfig(customConfigFile) { const configPath = Util.findUpConfig(customConfigFile); if (!configPath) { if (customConfigFile) { Util.logError(`The config file does not exist: ${customConfigFile}`); } // not found config return; } const configOptions = await resolveConfigOptions(configPath); if (!configOptions) { // failed to load config options if (customConfigFile) { Util.logError(`Failed to load config file: ${customConfigFile}`); } return; } Util.logInfo(`Loaded: ${EC.cyan(configPath)}`); // init options again this.options = { ... defaultOptions, ... configOptions, ... this.constructorOptions }; this.initOptions(true); } } /** create coverage report */ const MCR = function(options) { return new CoverageReport(options); }; MCR.CoverageReport = CoverageReport; MCR.getSnapshot = getSnapshot; MCR.diffSnapshot = diffSnapshot; MCR.WSSession = WSSession; MCR.CDPClient = CDPClient; MCR.Util = Util; module.exports = MCR;