From e58292a8af50f58483b1cedbed55a2410bc3a2ce Mon Sep 17 00:00:00 2001 From: cenfun Date: Fri, 1 Mar 2024 16:20:03 +0800 Subject: [PATCH] feat: support monocart report --- README.md | 7 ++ lib/commands/report.js | 7 ++ lib/monocart-report.js | 264 +++++++++++++++++++++++++++++++++++++++ lib/parse-args.js | 5 + package-lock.json | 112 ++++++++++++++++- package.json | 1 + test/integration.js | 124 ++++++++++++++++++ test/integration.js.snap | 252 ++++++++++++++++++++++++++++++++++++- 8 files changed, 760 insertions(+), 12 deletions(-) create mode 100644 lib/monocart-report.js diff --git a/README.md b/README.md index a266a1da1..8cbf277f7 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Here is a list of common options. Run `c8 --help` for the full list and document | `--per-file` | check thresholds per file | `boolean` | `false` | | `--temp-directory` | directory V8 coverage data is written to and read from | `string` | `process.env.NODE_V8_COVERAGE` | | `--clean` | should temp files be deleted before script execution | `boolean` | `true` | +| `--experimental-monocart` | see [section below](#using-monocart-coverage-reports-experimental) for more info | `boolean` | `false` | ## Checking for "full" source coverage using `--all` @@ -119,6 +120,12 @@ The `--100` flag can be set for the `check-coverage` as well: c8 check-coverage --100 ``` +## Using Monocart coverage reports (experimental) +[Monocart](https://github.com/cenfun/monocart-coverage-reports) will bring additional support for native V8 coverage reports, for example: +```sh +c8 --experimental-monocart --reporter=v8 --reporter=console-details node foo.js +``` + ## Ignoring Uncovered Lines, Functions, and Blocks Sometimes you might find yourself wanting to ignore uncovered portions of your diff --git a/lib/commands/report.js b/lib/commands/report.js index 7bdc48c3c..65e571ebe 100644 --- a/lib/commands/report.js +++ b/lib/commands/report.js @@ -1,5 +1,6 @@ const { checkCoverages } = require('./check-coverage') const Report = require('../report') +const monocartReport = require('../monocart-report') exports.command = 'report' @@ -18,6 +19,12 @@ exports.outputReport = async function (argv) { argv.branches = 100 argv.statements = 100 } + + if (argv.experimentalMonocart || process.env.EXPERIMENTAL_MONOCART) { + await monocartReport(argv) + return + } + const report = Report({ include: argv.include, exclude: argv.exclude, diff --git a/lib/monocart-report.js b/lib/monocart-report.js new file mode 100644 index 000000000..36ef48d61 --- /dev/null +++ b/lib/monocart-report.js @@ -0,0 +1,264 @@ +const Exclude = require('test-exclude') +const fs = require('fs') +const path = require('path') +const { fileURLToPath } = require('url') + +const { CoverageReport } = require('monocart-coverage-reports') + +module.exports = async (argv) => { + // console.log(argv); + const exclude = new Exclude({ + exclude: argv.exclude, + include: argv.include, + extension: argv.extension, + relativePath: !argv.allowExternal, + excludeNodeModules: argv.excludeNodeModules + }) + + // adapt coverage options + const coverageOptions = getCoverageOptions(argv, exclude) + const coverageReport = new CoverageReport(coverageOptions) + coverageReport.cleanCache() + + // read v8 coverage data from tempDirectory + await addV8Coverage(coverageReport, argv) + + // generate report + await coverageReport.generate() +} + +function getReports (argv) { + const reports = Array.isArray(argv.reporter) ? argv.reporter : [argv.reporter] + const reporterOptions = argv.reporterOptions || {} + + return reports.map((reportName) => { + const reportOptions = { + ...reporterOptions[reportName] + } + if (reportName === 'text') { + reportOptions.skipEmpty = false + reportOptions.skipFull = argv.skipFull + reportOptions.maxCols = process.stdout.columns || 100 + } + return [reportName, reportOptions] + }) +} + +// --all: add empty coverage for all files +function getAllOptions (argv, exclude) { + if (!argv.all) { + return + } + + const src = argv.src + const workingDirs = Array.isArray(src) ? src : (typeof src === 'string' ? [src] : [process.cwd()]) + return { + dir: workingDirs, + filter: (filePath) => { + return exclude.shouldInstrument(filePath) + } + } +} + +function getEntryFilter (argv, exclude) { + if (argv.entryFilter) { + return argv.entryFilter + } + return (entry) => { + return exclude.shouldInstrument(fileURLToPath(entry.url)) + } +} + +function getSourceFilter (argv, exclude) { + if (argv.sourceFilter) { + return argv.sourceFilter + } + return (sourcePath) => { + if (argv.excludeAfterRemap) { + // console.log(sourcePath) + return exclude.shouldInstrument(sourcePath) + } + return true + } +} + +function getCoverageOptions (argv, exclude) { + const reports = getReports(argv) + const allOptions = getAllOptions(argv, exclude) + + return { + logging: argv.logging, + name: argv.name, + inline: argv.inline, + lcov: argv.lcov, + outputDir: argv.reportsDir, + clean: argv.clean, + + reports, + all: allOptions, + + // use default value for istanbul + defaultSummarizer: 'pkg', + + entryFilter: getEntryFilter(argv, exclude), + + sourceFilter: getSourceFilter(argv, exclude), + + // sourcePath: (filePath) => { + // return path.resolve(filePath); + // }, + + onEnd: (coverageResults) => { + // console.log(`Coverage report generated: ${coverageResults.reportPath}`); + + if (!argv.checkCoverage) { + return + } + + // check thresholds + const thresholds = {} + const metrics = ['bytes', 'statements', 'branches', 'functions', 'lines'] + metrics.forEach((k) => { + if (argv[k]) { + thresholds[k] = argv[k] + } + }) + + const { summary, files } = coverageResults + + if (argv.perFile) { + files.forEach((file) => { + checkCoverage(file.summary, thresholds, file) + }) + } else { + checkCoverage(summary, thresholds) + } + } + } +} + +function checkCoverage (summary, thresholds, file) { + if (file && file.empty) { + process.exitCode = 1 + console.error( + 'ERROR: Empty coverage (untested file) does not meet threshold for ' + + path.relative('./', file.sourcePath).replace(/\\/g, '/') + ) + return + } + Object.keys(thresholds).forEach(key => { + const coverage = summary[key].pct + if (typeof coverage !== 'number') { + return + } + if (coverage < thresholds[key]) { + process.exitCode = 1 + if (file) { + console.error( + 'ERROR: Coverage for ' + key + ' (' + coverage + '%) does not meet threshold (' + thresholds[key] + '%) for ' + + path.relative('./', file.sourcePath).replace(/\\/g, '/') // standardize path for Windows. + ) + } else { + console.error('ERROR: Coverage for ' + key + ' (' + coverage + '%) does not meet global threshold (' + thresholds[key] + '%)') + } + } + }) +} + +function getFileSource (filePath) { + if (fs.existsSync(filePath)) { + return fs.readFileSync(filePath).toString('utf8') + } + return '' +} + +const resolveSourceMap = (sourceMap, url) => { + if (!sourceMap.sourcesContent) { + sourceMap.sourcesContent = sourceMap.sources.map(fileUrl => { + return getFileSource(fileURLToPath(fileUrl)) + }) + } + return sourceMap +} + +const resolveEntrySource = (entry, sourceMapCache = {}) => { + let source + const filePath = fileURLToPath(entry.url) + const extname = path.extname(filePath) + if (fs.existsSync(filePath)) { + source = fs.readFileSync(filePath).toString('utf8') + } + + // not for typescript + if (source && !['.ts', '.tsx'].includes(extname)) { + entry.source = source + return + } + + const sourcemapData = sourceMapCache[entry.url] + const lineLengths = sourcemapData && sourcemapData.lineLengths + + // for fake source file (can not parse to AST) + if (lineLengths) { + // get runtime code with ts-node + let fakeSource = '' + sourcemapData.lineLengths.forEach((length) => { + fakeSource += `${''.padEnd(length, '*')}\n` + }) + entry.fake = true + entry.source = fakeSource + } + + // Note: no runtime code in source map cache + // This is a problem for typescript +} + +const resolveEntrySourceMap = (entry, sourceMapCache = {}) => { + // sourcemap data + const sourcemapData = sourceMapCache[entry.url] + if (sourcemapData) { + if (sourcemapData.data) { + entry.sourceMap = resolveSourceMap(sourcemapData.data, entry.url) + } + } +} + +const collectCoverageData = (coverageList, entryFilterHandler, sourceMapCache = {}) => { + if (!coverageList.length) { + return + } + + // filter node internal files + coverageList = coverageList.filter((entry) => entry.url && entry.url.startsWith('file:')) + coverageList = coverageList.filter(entryFilterHandler) + + if (!coverageList.length) { + return + } + + for (const entry of coverageList) { + resolveEntrySource(entry, sourceMapCache) + resolveEntrySourceMap(entry, sourceMapCache) + } + + return coverageList +} + +async function addV8Coverage (coverageReport, argv) { + const entryFilterHandler = coverageReport.getEntryFilter() + const dir = argv.tempDirectory + const files = fs.readdirSync(dir) + for (const filename of files) { + const content = fs.readFileSync(path.resolve(dir, filename)).toString('utf-8') + if (!content) { + continue + } + const json = JSON.parse(content) + const coverageList = json.result + const sourceMapCache = json['source-map-cache'] + const coverageData = await collectCoverageData(coverageList, entryFilterHandler, sourceMapCache) + if (coverageData) { + await coverageReport.add(coverageData) + } + } +} diff --git a/lib/parse-args.js b/lib/parse-args.js index 21a9cd7a8..86199a2fa 100644 --- a/lib/parse-args.js +++ b/lib/parse-args.js @@ -158,6 +158,11 @@ function buildYargs (withCommands = false) { describe: 'supplying --merge-async will merge all v8 coverage reports asynchronously and incrementally. ' + 'This is to avoid OOM issues with Node.js runtime.' }) + .option('experimental-monocart', { + default: false, + type: 'boolean', + describe: 'Using Monocart coverage reports' + }) .pkgConf('c8') .demandCommand(1) .check((argv) => { diff --git a/package-lock.json b/package-lock.json index 2c60c943d..ae8645545 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", + "monocart-coverage-reports": "~2.6.1", "test-exclude": "^6.0.0", "v8-to-istanbul": "^9.0.0", "yargs": "^17.7.2", @@ -670,6 +671,11 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/console-grid": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/console-grid/-/console-grid-2.2.2.tgz", + "integrity": "sha512-ohlgXexdDTKLNsZz7DSJuCAwmRc8omSS61txOk39W3NOthgKGr1a1jJpZ5BCQe4PlrwMw01OvPQ1Bl3G7Y/uFg==" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -817,6 +823,11 @@ "node": ">=6.0.0" } }, + "node_modules/eight-colors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eight-colors/-/eight-colors-1.3.0.tgz", + "integrity": "sha512-hVoK898cR71ADj7L1LZWaECLaSkzzPtqGXIaKv4K6Pzb72QgjLVsQaNI+ELDQQshzFvgp5xTPkaYkPGqw3YR+g==" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2399,9 +2410,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -2688,6 +2699,11 @@ "node": ">=10" } }, + "node_modules/lz-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lz-utils/-/lz-utils-2.0.2.tgz", + "integrity": "sha512-i1PJN4hNEevkrvLMqNWCCac1BcB5SRaghywG7HVzWOyVkFOasLCG19ND1sY1F/ZEsM6SnGtoXyBWnmfqOM5r6g==" + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -2858,6 +2874,39 @@ "node": ">=10" } }, + "node_modules/monocart-code-viewer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/monocart-code-viewer/-/monocart-code-viewer-1.1.1.tgz", + "integrity": "sha512-V5nDGArctFO/GgJ2kuiHbJyxBaeNivJGUxhEAyCAn1nvip1GAsNDMJC6ms9vhwXiwwuYMXrd6ZZhvOItI11suA==" + }, + "node_modules/monocart-coverage-reports": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/monocart-coverage-reports/-/monocart-coverage-reports-2.6.1.tgz", + "integrity": "sha512-p4pulgiEF//KTXW+JSw6XALC2GVJzTGazzs/mPI3k76xRYkIosv9zcErBOKhhytvvcCS7o3I0JLFzWGr/fSKjg==", + "workspaces": [ + "packages/*", + "test" + ], + "dependencies": { + "console-grid": "~2.2.2", + "eight-colors": "~1.3.0", + "istanbul-lib-coverage": "~3.2.2", + "istanbul-lib-report": "~3.0.1", + "istanbul-reports": "~3.1.7", + "lz-utils": "~2.0.2", + "monocart-code-viewer": "~1.1.1", + "monocart-formatter": "~2.3.2", + "turbogrid": "~3.0.13" + }, + "bin": { + "mcr": "lib/cli.js" + } + }, + "node_modules/monocart-formatter": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/monocart-formatter/-/monocart-formatter-2.3.2.tgz", + "integrity": "sha512-Dr25minOW8yN6UVLP+6EHtJB0oHWimrssPUsPgfkBbSYUdQAnNZHltgHhST+EUNHI3Bl7uVwcIerL/kuBAoWYQ==" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4209,6 +4258,11 @@ "strip-bom": "^3.0.0" } }, + "node_modules/turbogrid": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/turbogrid/-/turbogrid-3.0.13.tgz", + "integrity": "sha512-8owt3hf29VDyW9excRMGccq/cmqspmvg8Zt6JgXw2uuq65drl50KXtGQpGevIbqQMnvltyfyLLJjzNnWK7tCVA==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5071,6 +5125,11 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "console-grid": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/console-grid/-/console-grid-2.2.2.tgz", + "integrity": "sha512-ohlgXexdDTKLNsZz7DSJuCAwmRc8omSS61txOk39W3NOthgKGr1a1jJpZ5BCQe4PlrwMw01OvPQ1Bl3G7Y/uFg==" + }, "convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5176,6 +5235,11 @@ "esutils": "^2.0.2" } }, + "eight-colors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eight-colors/-/eight-colors-1.3.0.tgz", + "integrity": "sha512-hVoK898cR71ADj7L1LZWaECLaSkzzPtqGXIaKv4K6Pzb72QgjLVsQaNI+ELDQQshzFvgp5xTPkaYkPGqw3YR+g==" + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -6319,9 +6383,9 @@ } }, "istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "requires": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -6559,6 +6623,11 @@ "yallist": "^4.0.0" } }, + "lz-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lz-utils/-/lz-utils-2.0.2.tgz", + "integrity": "sha512-i1PJN4hNEevkrvLMqNWCCac1BcB5SRaghywG7HVzWOyVkFOasLCG19ND1sY1F/ZEsM6SnGtoXyBWnmfqOM5r6g==" + }, "make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -6684,6 +6753,32 @@ } } }, + "monocart-code-viewer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/monocart-code-viewer/-/monocart-code-viewer-1.1.1.tgz", + "integrity": "sha512-V5nDGArctFO/GgJ2kuiHbJyxBaeNivJGUxhEAyCAn1nvip1GAsNDMJC6ms9vhwXiwwuYMXrd6ZZhvOItI11suA==" + }, + "monocart-coverage-reports": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/monocart-coverage-reports/-/monocart-coverage-reports-2.6.1.tgz", + "integrity": "sha512-p4pulgiEF//KTXW+JSw6XALC2GVJzTGazzs/mPI3k76xRYkIosv9zcErBOKhhytvvcCS7o3I0JLFzWGr/fSKjg==", + "requires": { + "console-grid": "~2.2.2", + "eight-colors": "~1.3.0", + "istanbul-lib-coverage": "~3.2.2", + "istanbul-lib-report": "~3.0.1", + "istanbul-reports": "~3.1.7", + "lz-utils": "~2.0.2", + "monocart-code-viewer": "~1.1.1", + "monocart-formatter": "~2.3.2", + "turbogrid": "~3.0.13" + } + }, + "monocart-formatter": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/monocart-formatter/-/monocart-formatter-2.3.2.tgz", + "integrity": "sha512-Dr25minOW8yN6UVLP+6EHtJB0oHWimrssPUsPgfkBbSYUdQAnNZHltgHhST+EUNHI3Bl7uVwcIerL/kuBAoWYQ==" + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -7652,6 +7747,11 @@ "strip-bom": "^3.0.0" } }, + "turbogrid": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/turbogrid/-/turbogrid-3.0.13.tgz", + "integrity": "sha512-8owt3hf29VDyW9excRMGccq/cmqspmvg8Zt6JgXw2uuq65drl50KXtGQpGevIbqQMnvltyfyLLJjzNnWK7tCVA==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 594464467..457e210d4 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", + "monocart-coverage-reports": "~2.6.1", "test-exclude": "^6.0.0", "v8-to-istanbul": "^9.0.0", "yargs": "^17.7.2", diff --git a/test/integration.js b/test/integration.js index d4aaeb132..99723c00d 100644 --- a/test/integration.js +++ b/test/integration.js @@ -685,6 +685,7 @@ beforeEach(function () { c8Path, '--all', '--exclude="test/*.js"', + '--exclude="tmp/monocart-*/**/*.js"', '--extension=.js', '--extension=.special', '--temp-directory=tmp/extension', @@ -696,5 +697,128 @@ beforeEach(function () { output.toString('utf8').should.matchSnapshot() }) }) + + describe('monocart report', () => { + it('monocart check normal', () => { + const { output } = spawnSync(nodePath, [ + c8Path, + '--experimental-monocart', + '--exclude="test/*.js"', + '--temp-directory=tmp/monocart-normal', + '--reports-dir=tmp/monocart-normal-reports', + '--reporter=v8', + '--reporter=console-details', + '--clean=false', + `--merge-async=${mergeAsync}`, + nodePath, + require.resolve('./fixtures/normal') + ]) + output.toString('utf8').should.matchSnapshot() + }) + + it('monocart check all', () => { + const { output } = spawnSync(nodePath, [ + c8Path, + '--experimental-monocart', + '--temp-directory=tmp/monocart-vanilla-all', + '--reports-dir=tmp/monocart-vanilla-all-reports', + '--reporter=v8', + '--reporter=console-details', + '--all', + '--include=test/fixtures/all/vanilla/**/*.js', + '--exclude=**/*.ts', + '--clean=false', + `--merge-async=${mergeAsync}`, + nodePath, + require.resolve('./fixtures/all/vanilla/main') + ]) + output.toString('utf8').should.matchSnapshot() + }) + + it('monocart check coverage', () => { + const { output, status } = spawnSync(nodePath, [ + c8Path, + '--experimental-monocart', + '--exclude="test/*.js"', + '--temp-directory=tmp/monocart-check-coverage', + '--reports-dir=tmp/monocart-check-coverage-reports', + '--reporter=v8', + '--reporter=console-details', + '--check-coverage', + '--statements=80', + '--branches=80', + '--lines=80', + '--clean=false', + `--merge-async=${mergeAsync}`, + nodePath, + require.resolve('./fixtures/normal') + ]) + status.should.equal(1) + output.toString('utf8').should.matchSnapshot() + }) + + it('monocart check coverage pre file', () => { + const { output, status } = spawnSync(nodePath, [ + c8Path, + '--experimental-monocart', + '--exclude="test/*.js"', + '--temp-directory=tmp/monocart-check-per-file', + '--reports-dir=tmp/monocart-check-per-file-reports', + '--reporter=v8', + '--reporter=console-details', + '--check-coverage', + '--statements=80', + '--branches=80', + '--lines=80', + '--per-file', + '--clean=false', + `--merge-async=${mergeAsync}`, + nodePath, + require.resolve('./fixtures/normal') + ]) + status.should.equal(1) + output.toString('utf8').should.matchSnapshot() + }) + + it('monocart check all and 100', () => { + const { output, status } = spawnSync(nodePath, [ + c8Path, + '--experimental-monocart', + '--temp-directory=tmp/monocart-all-100', + '--reports-dir=tmp/monocart-all-100-reports', + '--reporter=v8', + '--reporter=console-details', + '--all', + '--100', + '--per-file', + '--include=test/fixtures/all/vanilla/**/*.js', + '--exclude=**/*.ts', + '--clean=false', + `--merge-async=${mergeAsync}`, + nodePath, + require.resolve('./fixtures/all/vanilla/main') + ]) + status.should.equal(1) + output.toString('utf8').should.matchSnapshot() + }) + + it('check sourcemap', () => { + const { output } = spawnSync(nodePath, [ + c8Path, + '--experimental-monocart', + '--exclude="test/*.js"', + '--temp-directory=tmp/monocart-source-map', + '--reports-dir=tmp/monocart-source-map-reports', + '--reporter=v8', + '--reporter=text', + '--exclude-after-remap', + '--clean=false', + `--merge-async=${mergeAsync}`, + nodePath, + require.resolve('./fixtures/source-maps/branches/branches.typescript.js') + ]) + output.toString('utf8').should.matchSnapshot() + }) + }) }) }) diff --git a/test/integration.js.snap b/test/integration.js.snap index a9279c7e8..c6a7de965 100644 --- a/test/integration.js.snap +++ b/test/integration.js.snap @@ -156,7 +156,7 @@ hey ---------------------------------------|---------|----------|---------|---------|------------------------ File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ---------------------------------------|---------|----------|---------|---------|------------------------ -All files | 3.52 | 12.5 | 6.52 | 3.52 | +All files | 3.01 | 12.24 | 6.38 | 3.01 | c8 | 0 | 0 | 0 | 0 | index.js | 0 | 0 | 0 | 0 | 1 c8/bin | 0 | 0 | 0 | 0 | @@ -166,12 +166,13 @@ All files | 3.52 | 12.5 | 6.52 | 3.52 prettify.js | 0 | 0 | 0 | 0 | 1-2 sorter.js | 0 | 0 | 0 | 0 | 1-196 c8/lib | 0 | 0 | 0 | 0 | - parse-args.js | 0 | 0 | 0 | 0 | 1-224 + monocart-report.js | 0 | 0 | 0 | 0 | 1-264 + parse-args.js | 0 | 0 | 0 | 0 | 1-229 report.js | 0 | 0 | 0 | 0 | 1-402 source-map-from-file.js | 0 | 0 | 0 | 0 | 1-100 c8/lib/commands | 0 | 0 | 0 | 0 | check-coverage.js | 0 | 0 | 0 | 0 | 1-70 - report.js | 0 | 0 | 0 | 0 | 1-43 + report.js | 0 | 0 | 0 | 0 | 1-50 c8/test/fixtures | 30.97 | 37.5 | 21.42 | 30.97 | async.js | 100 | 100 | 100 | 100 | c8-ignore-next.js | 54.54 | 0 | 0 | 54.54 | 1,3-4,9,12,17-19,21-22 @@ -521,7 +522,7 @@ hey ---------------------------------------|---------|----------|---------|---------|------------------------ File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ---------------------------------------|---------|----------|---------|---------|------------------------ -All files | 3.52 | 12.5 | 6.52 | 3.52 | +All files | 3.01 | 12.24 | 6.38 | 3.01 | c8 | 0 | 0 | 0 | 0 | index.js | 0 | 0 | 0 | 0 | 1 c8/bin | 0 | 0 | 0 | 0 | @@ -531,12 +532,13 @@ All files | 3.52 | 12.5 | 6.52 | 3.52 prettify.js | 0 | 0 | 0 | 0 | 1-2 sorter.js | 0 | 0 | 0 | 0 | 1-196 c8/lib | 0 | 0 | 0 | 0 | - parse-args.js | 0 | 0 | 0 | 0 | 1-224 + monocart-report.js | 0 | 0 | 0 | 0 | 1-264 + parse-args.js | 0 | 0 | 0 | 0 | 1-229 report.js | 0 | 0 | 0 | 0 | 1-402 source-map-from-file.js | 0 | 0 | 0 | 0 | 1-100 c8/lib/commands | 0 | 0 | 0 | 0 | check-coverage.js | 0 | 0 | 0 | 0 | 1-70 - report.js | 0 | 0 | 0 | 0 | 1-43 + report.js | 0 | 0 | 0 | 0 | 1-50 c8/test/fixtures | 30.97 | 37.5 | 21.42 | 30.97 | async.js | 100 | 100 | 100 | 100 | c8-ignore-next.js | 54.54 | 0 | 0 | 54.54 | 1,3-4,9,12,17-19,21-22 @@ -745,6 +747,125 @@ All files | 100 | 100 | 100 | 100 | ," `; +exports[`c8 mergeAsync monocart report check sourcemap 1`] = ` +",reachable +a = true +a = false +------------------------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +------------------------|---------|----------|---------|---------|------------------- +All files | 82.35 | 70 | 100 | 81.25 | + branches.typescript.ts | 82.35 | 70 | 100 | 81.25 | 7,11,18 +------------------------|---------|----------|---------|---------|------------------- +," +`; + +exports[`c8 mergeAsync monocart report monocart check all 1`] = ` +",zero +positive +negative +┌───────────────────────────┬──────────┬────────────┬──────────┬───────────┬──────────┬─────────────────┐ +│ Name │ Bytes │ Statements │ Branches │ Functions │ Lines │ Uncovered Lines │ +├───────────────────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ test/fixtures/all/vanilla │ │ │ │ │ │ │ +│ ├ dir │ │ │ │ │ │ │ +│ │ └ unloaded.js │ 0.00 % │ 0.00 % │ │ 0.00 % │ 0.00 % │ 1-5 │ +│ ├ main.js │ 100.00 % │ 100.00 % │ │ │ 100.00 % │ │ +│ └ loaded.js │ 80.41 % │ 80.00 % │ 75.00 % │ 100.00 % │ 68.42 % │ 3-5,16-18 │ +├───────────────────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ Summary │ 65.64 % │ 70.59 % │ 75.00 % │ 50.00 % │ 62.96 % │ │ +└───────────────────────────┴──────────┴────────────┴──────────┴───────────┴──────────┴─────────────────┘ +," +`; + +exports[`c8 mergeAsync monocart report monocart check all and 100 1`] = ` +",zero +positive +negative +┌───────────────────────────┬──────────┬────────────┬──────────┬───────────┬──────────┬─────────────────┐ +│ Name │ Bytes │ Statements │ Branches │ Functions │ Lines │ Uncovered Lines │ +├───────────────────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ test/fixtures/all/vanilla │ │ │ │ │ │ │ +│ ├ dir │ │ │ │ │ │ │ +│ │ └ unloaded.js │ 0.00 % │ 0.00 % │ │ 0.00 % │ 0.00 % │ 1-5 │ +│ ├ main.js │ 100.00 % │ 100.00 % │ │ │ 100.00 % │ │ +│ └ loaded.js │ 80.41 % │ 80.00 % │ 75.00 % │ 100.00 % │ 68.42 % │ 3-5,16-18 │ +├───────────────────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ Summary │ 65.64 % │ 70.59 % │ 75.00 % │ 50.00 % │ 62.96 % │ │ +└───────────────────────────┴──────────┴────────────┴──────────┴───────────┴──────────┴─────────────────┘ +,ERROR: Empty coverage (untested file) does not meet threshold for test/fixtures/all/vanilla/dir/unloaded.js +ERROR: Coverage for statements (80%) does not meet threshold (100%) for test/fixtures/all/vanilla/loaded.js +ERROR: Coverage for branches (75%) does not meet threshold (100%) for test/fixtures/all/vanilla/loaded.js +ERROR: Coverage for lines (68.42%) does not meet threshold (100%) for test/fixtures/all/vanilla/loaded.js +" +`; + +exports[`c8 mergeAsync monocart report monocart check coverage 1`] = ` +",hey +i am a line of code +what +hey +what +hey +what +hey +┌───────────────┬──────────┬────────────┬──────────┬───────────┬──────────┬─────────────────┐ +│ Name │ Bytes │ Statements │ Branches │ Functions │ Lines │ Uncovered Lines │ +├───────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ test/fixtures │ │ │ │ │ │ │ +│ ├ normal.js │ 77.99 % │ 83.33 % │ 62.50 % │ 33.33 % │ 70.59 % │ 9,14-20 │ +│ └ async.js │ 100.00 % │ 100.00 % │ │ 100.00 % │ 100.00 % │ │ +├───────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ Summary │ 84.60 % │ 88.89 % │ 62.50 % │ 66.67 % │ 80.77 % │ │ +└───────────────┴──────────┴────────────┴──────────┴───────────┴──────────┴─────────────────┘ +,ERROR: Coverage for branches (62.5%) does not meet global threshold (80%) +" +`; + +exports[`c8 mergeAsync monocart report monocart check coverage pre file 1`] = ` +",hey +i am a line of code +what +hey +what +hey +what +hey +┌───────────────┬──────────┬────────────┬──────────┬───────────┬──────────┬─────────────────┐ +│ Name │ Bytes │ Statements │ Branches │ Functions │ Lines │ Uncovered Lines │ +├───────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ test/fixtures │ │ │ │ │ │ │ +│ ├ normal.js │ 77.99 % │ 83.33 % │ 62.50 % │ 33.33 % │ 70.59 % │ 9,14-20 │ +│ └ async.js │ 100.00 % │ 100.00 % │ │ 100.00 % │ 100.00 % │ │ +├───────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ Summary │ 84.60 % │ 88.89 % │ 62.50 % │ 66.67 % │ 80.77 % │ │ +└───────────────┴──────────┴────────────┴──────────┴───────────┴──────────┴─────────────────┘ +,ERROR: Coverage for branches (62.5%) does not meet threshold (80%) for test/fixtures/normal.js +ERROR: Coverage for lines (70.59%) does not meet threshold (80%) for test/fixtures/normal.js +" +`; + +exports[`c8 mergeAsync monocart report monocart check normal 1`] = ` +",hey +i am a line of code +what +hey +what +hey +what +hey +┌───────────────┬──────────┬────────────┬──────────┬───────────┬──────────┬─────────────────┐ +│ Name │ Bytes │ Statements │ Branches │ Functions │ Lines │ Uncovered Lines │ +├───────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ test/fixtures │ │ │ │ │ │ │ +│ ├ normal.js │ 77.99 % │ 83.33 % │ 62.50 % │ 33.33 % │ 70.59 % │ 9,14-20 │ +│ └ async.js │ 100.00 % │ 100.00 % │ │ 100.00 % │ 100.00 % │ │ +├───────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ Summary │ 84.60 % │ 88.89 % │ 62.50 % │ 66.67 % │ 80.77 % │ │ +└───────────────┴──────────┴────────────┴──────────┴───────────┴──────────┴─────────────────┘ +," +`; + exports[`c8 mergeAsync report generates report from existing temporary files 1`] = ` ",-----------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s @@ -1017,6 +1138,125 @@ All files | 100 | 100 | 100 | 100 | ," `; +exports[`c8 monocart report check sourcemap 1`] = ` +",reachable +a = true +a = false +------------------------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +------------------------|---------|----------|---------|---------|------------------- +All files | 82.35 | 70 | 100 | 81.25 | + branches.typescript.ts | 82.35 | 70 | 100 | 81.25 | 7,11,18 +------------------------|---------|----------|---------|---------|------------------- +," +`; + +exports[`c8 monocart report monocart check all 1`] = ` +",zero +positive +negative +┌───────────────────────────┬──────────┬────────────┬──────────┬───────────┬──────────┬─────────────────┐ +│ Name │ Bytes │ Statements │ Branches │ Functions │ Lines │ Uncovered Lines │ +├───────────────────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ test/fixtures/all/vanilla │ │ │ │ │ │ │ +│ ├ dir │ │ │ │ │ │ │ +│ │ └ unloaded.js │ 0.00 % │ 0.00 % │ │ 0.00 % │ 0.00 % │ 1-5 │ +│ ├ main.js │ 100.00 % │ 100.00 % │ │ │ 100.00 % │ │ +│ └ loaded.js │ 80.41 % │ 80.00 % │ 75.00 % │ 100.00 % │ 68.42 % │ 3-5,16-18 │ +├───────────────────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ Summary │ 65.64 % │ 70.59 % │ 75.00 % │ 50.00 % │ 62.96 % │ │ +└───────────────────────────┴──────────┴────────────┴──────────┴───────────┴──────────┴─────────────────┘ +," +`; + +exports[`c8 monocart report monocart check all and 100 1`] = ` +",zero +positive +negative +┌───────────────────────────┬──────────┬────────────┬──────────┬───────────┬──────────┬─────────────────┐ +│ Name │ Bytes │ Statements │ Branches │ Functions │ Lines │ Uncovered Lines │ +├───────────────────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ test/fixtures/all/vanilla │ │ │ │ │ │ │ +│ ├ dir │ │ │ │ │ │ │ +│ │ └ unloaded.js │ 0.00 % │ 0.00 % │ │ 0.00 % │ 0.00 % │ 1-5 │ +│ ├ main.js │ 100.00 % │ 100.00 % │ │ │ 100.00 % │ │ +│ └ loaded.js │ 80.41 % │ 80.00 % │ 75.00 % │ 100.00 % │ 68.42 % │ 3-5,16-18 │ +├───────────────────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ Summary │ 65.64 % │ 70.59 % │ 75.00 % │ 50.00 % │ 62.96 % │ │ +└───────────────────────────┴──────────┴────────────┴──────────┴───────────┴──────────┴─────────────────┘ +,ERROR: Empty coverage (untested file) does not meet threshold for test/fixtures/all/vanilla/dir/unloaded.js +ERROR: Coverage for statements (80%) does not meet threshold (100%) for test/fixtures/all/vanilla/loaded.js +ERROR: Coverage for branches (75%) does not meet threshold (100%) for test/fixtures/all/vanilla/loaded.js +ERROR: Coverage for lines (68.42%) does not meet threshold (100%) for test/fixtures/all/vanilla/loaded.js +" +`; + +exports[`c8 monocart report monocart check coverage 1`] = ` +",hey +i am a line of code +what +hey +what +hey +what +hey +┌───────────────┬──────────┬────────────┬──────────┬───────────┬──────────┬─────────────────┐ +│ Name │ Bytes │ Statements │ Branches │ Functions │ Lines │ Uncovered Lines │ +├───────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ test/fixtures │ │ │ │ │ │ │ +│ ├ normal.js │ 77.99 % │ 83.33 % │ 62.50 % │ 33.33 % │ 70.59 % │ 9,14-20 │ +│ └ async.js │ 100.00 % │ 100.00 % │ │ 100.00 % │ 100.00 % │ │ +├───────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ Summary │ 84.60 % │ 88.89 % │ 62.50 % │ 66.67 % │ 80.77 % │ │ +└───────────────┴──────────┴────────────┴──────────┴───────────┴──────────┴─────────────────┘ +,ERROR: Coverage for branches (62.5%) does not meet global threshold (80%) +" +`; + +exports[`c8 monocart report monocart check coverage pre file 1`] = ` +",hey +i am a line of code +what +hey +what +hey +what +hey +┌───────────────┬──────────┬────────────┬──────────┬───────────┬──────────┬─────────────────┐ +│ Name │ Bytes │ Statements │ Branches │ Functions │ Lines │ Uncovered Lines │ +├───────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ test/fixtures │ │ │ │ │ │ │ +│ ├ normal.js │ 77.99 % │ 83.33 % │ 62.50 % │ 33.33 % │ 70.59 % │ 9,14-20 │ +│ └ async.js │ 100.00 % │ 100.00 % │ │ 100.00 % │ 100.00 % │ │ +├───────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ Summary │ 84.60 % │ 88.89 % │ 62.50 % │ 66.67 % │ 80.77 % │ │ +└───────────────┴──────────┴────────────┴──────────┴───────────┴──────────┴─────────────────┘ +,ERROR: Coverage for branches (62.5%) does not meet threshold (80%) for test/fixtures/normal.js +ERROR: Coverage for lines (70.59%) does not meet threshold (80%) for test/fixtures/normal.js +" +`; + +exports[`c8 monocart report monocart check normal 1`] = ` +",hey +i am a line of code +what +hey +what +hey +what +hey +┌───────────────┬──────────┬────────────┬──────────┬───────────┬──────────┬─────────────────┐ +│ Name │ Bytes │ Statements │ Branches │ Functions │ Lines │ Uncovered Lines │ +├───────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ test/fixtures │ │ │ │ │ │ │ +│ ├ normal.js │ 77.99 % │ 83.33 % │ 62.50 % │ 33.33 % │ 70.59 % │ 9,14-20 │ +│ └ async.js │ 100.00 % │ 100.00 % │ │ 100.00 % │ 100.00 % │ │ +├───────────────┼──────────┼────────────┼──────────┼───────────┼──────────┼─────────────────┤ +│ Summary │ 84.60 % │ 88.89 % │ 62.50 % │ 66.67 % │ 80.77 % │ │ +└───────────────┴──────────┴────────────┴──────────┴───────────┴──────────┴─────────────────┘ +," +`; + exports[`c8 report generates report from existing temporary files 1`] = ` ",-----------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s