monocart-coverage-reports
Advanced tools
Comparing version 2.3.3 to 2.3.4
@@ -7,3 +7,2 @@ const { | ||
const { createBranches, collectBranches } = require('./branches.js'); | ||
const findInRanges = require('./find-in-ranges.js'); | ||
@@ -32,3 +31,3 @@ const getFunctionRange = (start, end, state) => { | ||
// find in functionRanges | ||
return findInRanges(start, end, functionRanges); | ||
return Util.findInRanges(start, end, functionRanges, 'startOffset', 'endOffset'); | ||
}; | ||
@@ -219,3 +218,3 @@ | ||
if (!count) { | ||
const coveredRange = findInRanges(startOffset, endOffset, coveredRanges); | ||
const coveredRange = Util.findInRanges(startOffset, endOffset, coveredRanges, 'startOffset', 'endOffset'); | ||
if (coveredRange) { | ||
@@ -222,0 +221,0 @@ count = 1; |
const Util = require('../utils/util.js'); | ||
const findInRanges = require('./find-in-ranges.js'); | ||
@@ -17,3 +16,3 @@ const findBranchBlock = (start, end, functionInfo) => { | ||
// "a" is in "|| a" | ||
const blockItem = findInRanges(start, end, blockRanges); | ||
const blockItem = Util.findInRanges(start, end, blockRanges, 'startOffset', 'endOffset'); | ||
if (blockItem) { | ||
@@ -112,3 +111,3 @@ return blockItem; | ||
const blockItem = findInRanges(start, end, blockRanges); | ||
const blockItem = Util.findInRanges(start, end, blockRanges, 'startOffset', 'endOffset'); | ||
if (blockItem) { | ||
@@ -275,4 +274,23 @@ node._state = { | ||
const updateBlockLocations = (locations) => { | ||
const noBlockList = []; | ||
let blockCount = 0; | ||
locations.forEach((item) => { | ||
if (item.block) { | ||
item.count = item.block.count; | ||
blockCount += item.count; | ||
return; | ||
} | ||
noBlockList.push(item); | ||
}); | ||
return { | ||
noBlockList, | ||
blockCount | ||
}; | ||
}; | ||
// const a = tf1 ? 'true' : 'false'; | ||
const ConditionalExpression = (group, parentCount, noBlockList, blockCount) => { | ||
const ConditionalExpression = (locations, parentCount) => { | ||
const { noBlockList, blockCount } = updateBlockLocations(locations); | ||
if (!noBlockList.length) { | ||
@@ -288,3 +306,4 @@ return; | ||
const IfStatement = (group, parentCount, noBlockList, blockCount) => { | ||
const IfStatement = (locations, parentCount) => { | ||
const { noBlockList, blockCount } = updateBlockLocations(locations); | ||
if (!noBlockList.length) { | ||
@@ -303,3 +322,4 @@ return; | ||
// const b = tf2 || tf1 || a; | ||
const LogicalExpression = (group, parentCount, noBlockList, blockCount) => { | ||
const LogicalExpression = (locations, parentCount) => { | ||
const { noBlockList, blockCount } = updateBlockLocations(locations); | ||
if (!noBlockList.length) { | ||
@@ -312,11 +332,6 @@ return; | ||
}); | ||
// const start = group.start; | ||
// if (start > 7874 && start < 7922) { | ||
// console.log(group.type, parentCount, 'locations', group.locations); | ||
// } | ||
}; | ||
const SwitchStatement = (group, parentCount, noBlockList, blockCount) => { | ||
const SwitchStatement = (locations, parentCount) => { | ||
const { noBlockList, blockCount } = updateBlockLocations(locations); | ||
if (!noBlockList.length) { | ||
@@ -331,2 +346,8 @@ return; | ||
const AssignmentPattern = (locations, parentCount) => { | ||
locations.forEach((item) => { | ||
item.count = parentCount; | ||
}); | ||
}; | ||
// ======================================================================================= | ||
@@ -373,13 +394,2 @@ | ||
const noBlockList = []; | ||
let blockCount = 0; | ||
locations.forEach((item) => { | ||
if (item.block) { | ||
item.count = item.block.count; | ||
blockCount += item.count; | ||
return; | ||
} | ||
noBlockList.push(item); | ||
}); | ||
const handlers = { | ||
@@ -389,3 +399,4 @@ ConditionalExpression, | ||
LogicalExpression, | ||
SwitchStatement | ||
SwitchStatement, | ||
AssignmentPattern | ||
}; | ||
@@ -395,3 +406,3 @@ | ||
if (handler) { | ||
handler(group, parentCount, noBlockList, blockCount); | ||
handler(locations, parentCount); | ||
} | ||
@@ -398,0 +409,0 @@ |
@@ -23,3 +23,2 @@ /** | ||
const InfoFunction = require('./info-function.js'); | ||
const findInRanges = require('./find-in-ranges.js'); | ||
@@ -33,3 +32,3 @@ // ======================================================================================================== | ||
} | ||
const range = findInRanges(item.start, item.end, ignoredRanges); | ||
const range = Util.findInRanges(item.start, item.end, ignoredRanges, 'start', 'end'); | ||
if (range) { | ||
@@ -44,5 +43,4 @@ // console.log(item, range); | ||
const updateLinesCount = (bytes, locator, lineMap) => { | ||
const applyBytesToLines = (bytes, locator, lineMap) => { | ||
bytes.forEach((range) => { | ||
const { | ||
@@ -52,2 +50,7 @@ start, end, count, ignored | ||
// no need handle ignored and uncovered byte | ||
if (count === 0 && ignored) { | ||
return; | ||
} | ||
const sLoc = locator.offsetToLocation(start); | ||
@@ -60,79 +63,106 @@ const eLoc = locator.offsetToLocation(end); | ||
lines.forEach((it) => { | ||
const line = lineMap.get(it.line); | ||
if (!line) { | ||
const lineItem = lineMap.get(it.line); | ||
if (!lineItem) { | ||
// not found line, could be comment or blank line | ||
return; | ||
} | ||
if (lineItem.ignored) { | ||
return; | ||
} | ||
// from outside into inside, uncovered is certain | ||
// default is covered | ||
if (it.entire) { | ||
line.covered = count > 0; | ||
line.count = count; | ||
it.count = count; | ||
if (ignored) { | ||
if (!line.covered) { | ||
line.ignored = true; | ||
} | ||
} | ||
// default is covered, so only focus on | ||
// 1, last covered count | ||
// 2, uncovered entire and pieces | ||
if (count > 0) { | ||
lineItem.coveredCount = count; | ||
} else { | ||
if (!ignored) { | ||
if (count > 0) { | ||
line.count = count; | ||
} else { | ||
// not covered if any count = 0 | ||
line.covered = false; | ||
} | ||
if (it.entire) { | ||
lineItem.uncoveredEntire = it; | ||
} else { | ||
lineItem.uncoveredPieces.push(it); | ||
} | ||
} | ||
// if (!line.history) { | ||
// line.history = []; | ||
// } | ||
// line.history.push(`${it.entire}-${count}`); | ||
}); | ||
}); | ||
// if (state.sourcePath.endsWith('component.js')) { | ||
// console.log('===========================================', state.sourcePath); | ||
// } | ||
}; | ||
const handleLinesCoverage = (bytes, locator) => { | ||
const handleLinesCoverage = (bytes, locator, ignoredRanges) => { | ||
const lines = []; | ||
// line 1 based | ||
const lineMap = new Map(); | ||
// init lines | ||
let blankCount = 0; | ||
let commentCount = 0; | ||
locator.lines.forEach((it) => { | ||
// line 1 based | ||
const lineMap = new Map(); | ||
locator.lines.forEach((lineItem) => { | ||
// exclude blank and comment | ||
if (it.blank) { | ||
if (lineItem.blank) { | ||
blankCount += 1; | ||
return; | ||
} | ||
if (it.comment) { | ||
if (lineItem.comment) { | ||
commentCount += 1; | ||
return; | ||
} | ||
const ignored = Util.findInRanges(lineItem.start, lineItem.end, ignoredRanges); | ||
if (ignored) { | ||
return; | ||
} | ||
// line 1-base | ||
const line = it.line + 1; | ||
const line = lineItem.line + 1; | ||
lineItem.coveredCount = 1; | ||
lineItem.uncoveredEntire = null; | ||
lineItem.uncoveredPieces = []; | ||
lineMap.set(line, lineItem); | ||
}); | ||
applyBytesToLines(bytes, locator, lineMap); | ||
const lines = []; | ||
// data lines for codecov | ||
// https://docs.codecov.com/docs/codecov-custom-coverage-format | ||
const dataLines = {}; | ||
// no ignore items | ||
lineMap.forEach((lineItem, line) => { | ||
const { | ||
uncoveredEntire, uncoveredPieces, coveredCount | ||
} = lineItem; | ||
// default count to 1, both js and css | ||
const lineInfo = new InfoLine(line, it.length, 1); | ||
lineMap.set(line, lineInfo); | ||
lines.push(lineInfo); | ||
let count = 1; | ||
// full covered true/false for entire line | ||
let covered = true; | ||
if (uncoveredEntire) { | ||
count = 0; | ||
covered = false; | ||
} else { | ||
count = coveredCount; | ||
const uncoveredLen = uncoveredPieces.length; | ||
if (uncoveredLen > 0) { | ||
covered = false; | ||
// uncovered | ||
count = `1/${uncoveredLen + 1}`; | ||
} | ||
} | ||
// data lines | ||
dataLines[line] = count; | ||
// exclude indent? | ||
const infoLine = new InfoLine(line, lineItem.length, count, covered); | ||
lines.push(infoLine); | ||
}); | ||
updateLinesCount(bytes, locator, lineMap); | ||
return { | ||
lines, | ||
dataLines, | ||
blankCount, | ||
@@ -193,7 +223,4 @@ commentCount | ||
// the lines not includes ignored | ||
lines.forEach((ln) => { | ||
if (ln.ignored) { | ||
// console.log(ln); | ||
return; | ||
} | ||
v8Lines.total += 1; | ||
@@ -244,2 +271,4 @@ // full line covered | ||
data.ignores = ignoredRanges; | ||
// data bytes is start/end/count object | ||
@@ -285,5 +314,7 @@ handleIgnoredRanges(data.bytes, ignoredRanges); | ||
const { | ||
lines, blankCount, commentCount | ||
} = handleLinesCoverage(data.bytes, locator); | ||
lines, dataLines, blankCount, commentCount | ||
} = handleLinesCoverage(data.bytes, locator, ignoredRanges); | ||
data.lines = dataLines; | ||
// ========================================== | ||
@@ -312,17 +343,6 @@ // v8 data and summary | ||
// for codecov https://docs.codecov.com/docs/codecov-custom-coverage-format | ||
data.lines = {}; | ||
lines.filter((it) => !it.ignored).forEach((line, index) => { | ||
// the lines not includes ignored | ||
lines.forEach((line, index) => { | ||
istanbulData.statementMap[`${index}`] = line.generate(); | ||
istanbulData.s[`${index}`] = line.count; | ||
let count = 0; | ||
if (line.covered) { | ||
count = line.count; | ||
} else if (!line.covered && line.count > 0) { | ||
count = '1/2'; | ||
} | ||
// 1-base | ||
data.lines[`${line.line}`] = count; | ||
}); | ||
@@ -329,0 +349,0 @@ |
@@ -5,3 +5,3 @@ const Util = require('../utils/util.js'); | ||
// v8 ignore next N | ||
// v-8 ignore next N | ||
if (content) { | ||
@@ -24,6 +24,22 @@ const n = parseInt(content); | ||
// v8 ignore next | ||
// v-8 ignore next | ||
return 1; | ||
}; | ||
const extendStart = (start, source) => { | ||
// extend left, include | ||
while (start > 0 && Util.isBlank(source[start - 1])) { | ||
start -= 1; | ||
} | ||
return start; | ||
}; | ||
const extendEnd = (end, source, maxLength) => { | ||
// extend right, exclude | ||
while (end < maxLength && Util.isBlank(source[end])) { | ||
end += 1; | ||
} | ||
return end; | ||
}; | ||
const addNextIgnore = (list, content, item, locator) => { | ||
@@ -33,8 +49,5 @@ | ||
const maxLength = source.length; | ||
let startOffset = item.start; | ||
// extend left | ||
while (startOffset > 0 && Util.isBlank(source[startOffset - 1])) { | ||
startOffset -= 1; | ||
} | ||
const start = extendStart(item.start, source); | ||
// findLine 0-base | ||
@@ -49,7 +62,3 @@ const currentLine = lineParser.findLine(item.end); | ||
const endLine = locator.getLine(line + n); | ||
let endOffset = endLine ? endLine.end : maxLength; | ||
// extend right | ||
while (endOffset < maxLength && Util.isBlank(source[endOffset + 1])) { | ||
endOffset += 1; | ||
} | ||
const end = extendEnd(endLine ? endLine.end : maxLength, source, maxLength); | ||
@@ -59,4 +68,4 @@ const nextItem = { | ||
n, | ||
startOffset, | ||
endOffset | ||
start, | ||
end | ||
}; | ||
@@ -77,2 +86,3 @@ | ||
} | ||
// istanbul ignore has different rules, can not support it | ||
}; | ||
@@ -85,3 +95,4 @@ | ||
const { lineParser } = locator; | ||
const { lineParser, source } = locator; | ||
const maxLength = source.length; | ||
@@ -111,5 +122,5 @@ const comments = lineParser.comments; | ||
if (ignoreStart) { | ||
// v8 ignore stop | ||
// v-8 ignore stop | ||
if (content === 'stop') { | ||
ignoreStart.endOffset = end; | ||
ignoreStart.end = extendEnd(end, source, maxLength); | ||
ignoreStart = null; | ||
@@ -120,3 +131,3 @@ } | ||
// v8 ignore start | ||
// v-8 ignore start | ||
if (content === 'start') { | ||
@@ -126,4 +137,4 @@ // add first for sort by start | ||
type: 'start-stop', | ||
startOffset: start, | ||
endOffset: start | ||
start: extendStart(start, source), | ||
end: start | ||
}; | ||
@@ -134,3 +145,3 @@ list.push(ignoreStart); | ||
// v8 ignore next N | ||
// v-8 ignore next N | ||
const nextKey = 'next'; | ||
@@ -146,3 +157,3 @@ if (content.startsWith(nextKey)) { | ||
if (ignoreStart) { | ||
ignoreStart.endOffset = locator.source.length; | ||
ignoreStart.end = locator.source.length; | ||
ignoreStart = null; | ||
@@ -149,0 +160,0 @@ } |
module.exports = class InfoLine { | ||
constructor(line, column, count = 1) { | ||
constructor(line, column, count, covered) { | ||
// 1 based | ||
@@ -8,3 +8,3 @@ this.line = line; | ||
// covered full line, could be false even count > 0 | ||
this.covered = count > 0; | ||
this.covered = covered; | ||
} | ||
@@ -11,0 +11,0 @@ |
@@ -251,2 +251,37 @@ const Util = { | ||
findInRanges: (startPos, endPos, rangeList, startKey = 'start', endKey = 'end') => { | ||
if (!Util.isList(rangeList)) { | ||
return; | ||
} | ||
const quickFindRange = (position, ranges) => { | ||
let start = 0; | ||
let end = ranges.length - 1; | ||
while (end - start > 1) { | ||
const i = Math.floor((start + end) * 0.5); | ||
const item = ranges[i]; | ||
if (position < item[startKey]) { | ||
end = i; | ||
continue; | ||
} | ||
if (position > item[endKey]) { | ||
start = i; | ||
continue; | ||
} | ||
return ranges[i]; | ||
} | ||
// last two items, less is start | ||
const endItem = ranges[end]; | ||
if (position < endItem[startKey]) { | ||
return ranges[start]; | ||
} | ||
return ranges[end]; | ||
}; | ||
const range = quickFindRange(startPos, rangeList); | ||
if (startPos >= range[startKey] && endPos <= range[endKey]) { | ||
return range; | ||
} | ||
}, | ||
getRangeLines: (sLoc, eLoc) => { | ||
@@ -277,3 +312,3 @@ | ||
entire, | ||
range: { | ||
pieces: { | ||
start: sLoc.column, | ||
@@ -294,3 +329,3 @@ end: sLoc.length | ||
entire, | ||
range: { | ||
pieces: { | ||
start: sLoc.column, | ||
@@ -313,3 +348,3 @@ end: eLoc.column | ||
entire, | ||
range: { | ||
pieces: { | ||
start: eLoc.indent, | ||
@@ -339,3 +374,3 @@ end: eLoc.column | ||
entire: true | ||
// no range for entire line | ||
// no pieces for entire line | ||
}); | ||
@@ -342,0 +377,0 @@ } |
{ | ||
"name": "monocart-coverage-reports", | ||
"version": "2.3.3", | ||
"version": "2.3.4", | ||
"description": "Monocart coverage reports", | ||
@@ -5,0 +5,0 @@ "main": "./lib/index.js", |
@@ -32,4 +32,4 @@ # Monocart Coverage Reports | ||
- [How Monocart Works](#how-monocart-works) | ||
* [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap) | ||
* [Integration](#integration) - Playwright, Jest, Vitest, Codecov, Coveralls, Sonar Cloud | ||
* [Istanbul Introduction](#istanbul-introduction) | ||
* [Thanks](#thanks) | ||
@@ -306,3 +306,3 @@ | ||
## Manually Resolve the Sourcemap | ||
> If the `js` file is loaded with `addScriptTag` [API](https://playwright.dev/docs/api/class-page#page-add-script-tag), then its sourcemap file may not work. You can try to manually read the sourcemap file before the coverage data is added to the report. | ||
> Sometimes, the sourcemap file cannot be successfully loaded with the `sourceMappingURL`, you can try to manually read the sourcemap file before the coverage data is added to the report. | ||
```js | ||
@@ -375,24 +375,30 @@ const jsCoverage = await page.coverage.stopJSCoverage(); | ||
## Multiprocessing Support | ||
The data will be added to `[outputDir]/.cache`, and the cache will be removed after reports generated. | ||
- sub process 1 | ||
> The data will be added to `[outputDir]/.cache`, After the generation of the report, this data will be removed unless debugging has been enabled or a raw report has been used, see [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap) | ||
- Main process, before the start of testing | ||
```js | ||
// clean previous cache before the start of testing (option) | ||
const MCR = require('monocart-coverage-reports'); | ||
const options = require('path-to/same-options.js'); | ||
const coverageReport = MCR(options); | ||
await coverageReport.add(coverageData1); | ||
MCR(options).cleanCache(); | ||
``` | ||
- sub process 2 | ||
- Sub process 1, testing stage 1 | ||
```js | ||
const MCR = require('monocart-coverage-reports'); | ||
const options = require('path-to/same-options.js'); | ||
const coverageReport = MCR(options); | ||
await coverageReport.add(coverageData2); | ||
await MCR(options).add(coverageData1); | ||
``` | ||
- main process | ||
- Sub process 2, testing stage 2 | ||
```js | ||
// after all sub processes finished | ||
const MCR = require('monocart-coverage-reports'); | ||
const options = require('path-to/same-options.js'); | ||
await MCR(options).add(coverageData2); | ||
``` | ||
- Main process, after the completion of testing | ||
```js | ||
// generate coverage reports after the completion of testing | ||
const MCR = require('monocart-coverage-reports'); | ||
const options = require('path-to/same-options.js'); | ||
const coverageReport = MCR(options); | ||
@@ -484,3 +490,3 @@ const coverageResults = await coverageReport.generate(); | ||
if (platform === 'linux') { | ||
console.info('hello linux'); | ||
console.log('hello linux'); | ||
} | ||
@@ -584,2 +590,21 @@ ``` | ||
## Debug for Coverage and Sourcemap | ||
> Sometimes, the coverage is not what we expect. The next step is to figure out why, and we can easily find out the answer step by step through debugging. | ||
- Start debugging for v8 report with option `logging: 'debug'` | ||
```js | ||
const coverageOptions = { | ||
logging: 'debug', | ||
reports: [ | ||
['v8'], | ||
['console-summary'] | ||
] | ||
}; | ||
``` | ||
When `logging` is `debug`, the raw report data will be preserved in `[outputDir]/.cache` or `[outputDir]/raw` if `raw` report is used. And the dist file will be preserved in the V8 list, and by opening the browser's devtool, it makes data verification visualization effortless. | ||
![](./test/debug-coverage.png) | ||
- Check sourcemap with [Source Map Visualization](https://evanw.github.io/source-map-visualization/) | ||
![](./test/debug-sourcemap.png) | ||
## Integration | ||
@@ -609,2 +634,8 @@ - [monocart-reporter](https://github.com/cenfun/monocart-reporter) - A [Playwright](https://github.com/microsoft/playwright) custom reporter, supports generating [Code Coverage Report](https://github.com/cenfun/monocart-reporter?#code-coverage-report) | ||
- [![Coverage Status](https://coveralls.io/repos/github/cenfun/monocart-coverage-reports/badge.svg?branch=main)](https://coveralls.io/github/cenfun/monocart-coverage-reports?branch=main) | ||
```js | ||
const coverageOptions = { | ||
outputDir: "./coverage-reports", | ||
lcov: true | ||
}; | ||
``` | ||
- Github Actions example: | ||
@@ -636,11 +667,8 @@ ```yml | ||
``` | ||
- Integration with any testing framework | ||
- First, you need to collect coverage data when any stage of the test is completed. Then, add the coverage data to the coverage report. | ||
- Upon the completion of all tests, generate the coverage report. | ||
- see [Multiprocessing Support](#multiprocessing-support) | ||
## Istanbul Introduction | ||
- [Istanbul coverage report](https://istanbul.js.org/) - Instrumenting source codes and generating coverage reports | ||
- [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul) | ||
- [istanbul-reports](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) | ||
- [Code Coverage Introduction](https://docs.cypress.io/guides/tooling/code-coverage) | ||
## Thanks | ||
- Special thanks to [@edumserrano](https://github.com/edumserrano) |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
899305
7003
667
30
0