-
Notifications
You must be signed in to change notification settings - Fork 30.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
test_runner: refactor coverage report output for readability #47791
Merged
nodejs-github-bot
merged 11 commits into
nodejs:main
from
dmnsgn:coverage-spec-table-format
Jun 7, 2023
Merged
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
d177485
test_runner: refactor coverage report output for readability
dmnsgn be8cdf8
lib: add yellow to colors util
dmnsgn e024399
test_runner: use yellow from util/colors
dmnsgn 6bbe5b7
test_runner: memoize coverage report table line
dmnsgn 1f9ad76
test_runner: use primordial StringPrototypeRepeat
dmnsgn d22b485
test_runner: extract horizontal ellipsis unicode character into a con…
dmnsgn 04d2b78
test_runner: use StringPrototypeSlice primordial instead of substring…
dmnsgn b7cdf96
test_runner: default coverage report availableWidth to Infinity
dmnsgn dbafa5b
test_runner: pass pad, truncate and coverage as arguments to coverage…
dmnsgn 98a327b
test_runner: remove quotes for coverageColors properties
dmnsgn fdade11
Update lib/internal/test_runner/utils.js
MoLow File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,20 +3,28 @@ const { | |
ArrayPrototypeJoin, | ||
ArrayPrototypeMap, | ||
ArrayPrototypePush, | ||
ArrayPrototypeReduce, | ||
ObjectGetOwnPropertyDescriptor, | ||
MathFloor, | ||
MathMax, | ||
MathMin, | ||
NumberPrototypeToFixed, | ||
SafePromiseAllReturnArrayLike, | ||
RegExp, | ||
RegExpPrototypeExec, | ||
SafeMap, | ||
StringPrototypePadStart, | ||
StringPrototypePadEnd, | ||
StringPrototypeRepeat, | ||
StringPrototypeSlice, | ||
} = primordials; | ||
|
||
const { basename, relative } = require('path'); | ||
const { createWriteStream } = require('fs'); | ||
const { pathToFileURL } = require('internal/url'); | ||
const { createDeferredPromise } = require('internal/util'); | ||
const { getOptionValue } = require('internal/options'); | ||
const { green, red, white, shouldColorize } = require('internal/util/colors'); | ||
const { green, yellow, red, white, shouldColorize } = require('internal/util/colors'); | ||
|
||
const { | ||
codes: { | ||
|
@@ -27,6 +35,13 @@ const { | |
} = require('internal/errors'); | ||
const { compose } = require('stream'); | ||
|
||
const coverageColors = { | ||
__proto__: null, | ||
high: green, | ||
medium: yellow, | ||
low: red, | ||
}; | ||
|
||
const kMultipleCallbackInvocations = 'multipleCallbackInvocations'; | ||
const kRegExpPattern = /^\/(.*)\/([a-z]*)$/; | ||
const kSupportedFileExtensions = /\.[cm]?js$/; | ||
|
@@ -256,45 +271,139 @@ function countCompletedTest(test, harness = test.root.harness) { | |
} | ||
|
||
|
||
function coverageThreshold(coverage, color) { | ||
coverage = NumberPrototypeToFixed(coverage, 2); | ||
if (color) { | ||
if (coverage > 90) return `${green}${coverage}${color}`; | ||
if (coverage < 50) return `${red}${coverage}${color}`; | ||
const memo = new SafeMap(); | ||
function addTableLine(prefix, width) { | ||
const key = `${prefix}-${width}`; | ||
let value = memo.get(key); | ||
if (value === undefined) { | ||
value = `${prefix}${StringPrototypeRepeat('-', width)}\n`; | ||
memo.set(key, value); | ||
} | ||
return coverage; | ||
|
||
return value; | ||
} | ||
|
||
const kHorizontalEllipsis = '\u2026'; | ||
function truncateStart(string, width) { | ||
return string.length > width ? `${kHorizontalEllipsis}${StringPrototypeSlice(string, string.length - width + 1)}` : string; | ||
} | ||
|
||
function truncateEnd(string, width) { | ||
return string.length > width ? `${StringPrototypeSlice(string, 0, width - 1)}${kHorizontalEllipsis}` : string; | ||
} | ||
|
||
function formatLinesToRanges(values) { | ||
return ArrayPrototypeMap(ArrayPrototypeReduce(values, (prev, current, index, array) => { | ||
if ((index > 0) && ((current - array[index - 1]) === 1)) { | ||
prev[prev.length - 1][1] = current; | ||
} else { | ||
prev.push([current]); | ||
} | ||
return prev; | ||
}, []), (range) => range.join('-')); | ||
MoLow marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
function formatUncoveredLines(lines, table) { | ||
if (table) return ArrayPrototypeJoin(formatLinesToRanges(lines), ' '); | ||
return ArrayPrototypeJoin(lines, ', '); | ||
} | ||
|
||
function getCoverageReport(pad, summary, symbol, color) { | ||
let report = `${color}${pad}${symbol}start of coverage report\n`; | ||
const kColumns = ['line %', 'branch %', 'funcs %']; | ||
const kColumnsKeys = ['coveredLinePercent', 'coveredBranchPercent', 'coveredFunctionPercent']; | ||
const kSeparator = ' | '; | ||
|
||
function getCoverageReport(pad, summary, symbol, color, table) { | ||
const prefix = `${pad}${symbol}`; | ||
let report = `${color}${prefix}start of coverage report\n`; | ||
|
||
let filePadLength; | ||
let columnPadLengths = []; | ||
let uncoveredLinesPadLength; | ||
let tableWidth; | ||
|
||
if (table) { | ||
// Get expected column sizes | ||
filePadLength = table && ArrayPrototypeReduce(summary.files, (acc, file) => | ||
MathMax(acc, relative(summary.workingDirectory, file.path).length), 0); | ||
filePadLength = MathMax(filePadLength, 'file'.length); | ||
const fileWidth = filePadLength + 2; | ||
|
||
columnPadLengths = ArrayPrototypeMap(kColumns, (column) => (table ? MathMax(column.length, 6) : 0)); | ||
const columnsWidth = ArrayPrototypeReduce(columnPadLengths, (acc, columnPadLength) => acc + columnPadLength + 3, 0); | ||
|
||
uncoveredLinesPadLength = table && ArrayPrototypeReduce(summary.files, (acc, file) => | ||
MathMax(acc, formatUncoveredLines(file.uncoveredLineNumbers, table).length), 0); | ||
uncoveredLinesPadLength = MathMax(uncoveredLinesPadLength, 'uncovered lines'.length); | ||
const uncoveredLinesWidth = uncoveredLinesPadLength + 2; | ||
|
||
tableWidth = fileWidth + columnsWidth + uncoveredLinesWidth; | ||
|
||
// Fit with sensible defaults | ||
const availableWidth = (process.stdout.columns || Infinity) - prefix.length; | ||
const columnsExtras = tableWidth - availableWidth; | ||
if (table && columnsExtras > 0) { | ||
// Ensure file name is sufficiently visible | ||
const minFilePad = MathMin(8, filePadLength); | ||
filePadLength -= MathFloor(columnsExtras * 0.2); | ||
filePadLength = MathMax(filePadLength, minFilePad); | ||
|
||
// Get rest of available space, subtracting margins | ||
uncoveredLinesPadLength = MathMax(availableWidth - columnsWidth - (filePadLength + 2) - 2, 1); | ||
|
||
// Update table width | ||
tableWidth = availableWidth; | ||
} else { | ||
uncoveredLinesPadLength = Infinity; | ||
} | ||
} | ||
|
||
|
||
function getCell(string, width, pad, truncate, coverage) { | ||
if (!table) return string; | ||
|
||
let result = string; | ||
if (pad) result = pad(result, width); | ||
if (truncate) result = truncate(result, width); | ||
if (color && coverage !== undefined) { | ||
if (coverage > 90) return `${coverageColors.high}${result}${color}`; | ||
if (coverage > 50) return `${coverageColors.medium}${result}${color}`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if these values should be configurable someway... |
||
return `${coverageColors.low}${result}${color}`; | ||
} | ||
return result; | ||
} | ||
|
||
report += `${pad}${symbol}file | line % | branch % | funcs % | uncovered lines\n`; | ||
// Head | ||
if (table) report += addTableLine(prefix, tableWidth); | ||
report += `${prefix}${getCell('file', filePadLength, StringPrototypePadEnd, truncateEnd)}${kSeparator}` + | ||
`${ArrayPrototypeJoin(ArrayPrototypeMap(kColumns, (column, i) => getCell(column, columnPadLengths[i], StringPrototypePadStart)), kSeparator)}${kSeparator}` + | ||
`${getCell('uncovered lines', uncoveredLinesPadLength, false, truncateEnd)}\n`; | ||
if (table) report += addTableLine(prefix, tableWidth); | ||
|
||
// Body | ||
for (let i = 0; i < summary.files.length; ++i) { | ||
const { | ||
path, | ||
coveredLinePercent, | ||
coveredBranchPercent, | ||
coveredFunctionPercent, | ||
uncoveredLineNumbers, | ||
} = summary.files[i]; | ||
const relativePath = relative(summary.workingDirectory, path); | ||
const lines = coverageThreshold(coveredLinePercent, color); | ||
const branches = coverageThreshold(coveredBranchPercent, color); | ||
const functions = coverageThreshold(coveredFunctionPercent, color); | ||
const uncovered = ArrayPrototypeJoin(uncoveredLineNumbers, ', '); | ||
|
||
report += `${pad}${symbol}${relativePath} | ${lines} | ${branches} | ` + | ||
`${functions} | ${uncovered}\n`; | ||
const file = summary.files[i]; | ||
const relativePath = relative(summary.workingDirectory, file.path); | ||
|
||
let fileCoverage = 0; | ||
const coverages = ArrayPrototypeMap(kColumnsKeys, (columnKey) => { | ||
const percent = file[columnKey]; | ||
fileCoverage += percent; | ||
return percent; | ||
}); | ||
fileCoverage /= kColumnsKeys.length; | ||
|
||
report += `${prefix}${getCell(relativePath, filePadLength, StringPrototypePadEnd, truncateStart, fileCoverage)}${kSeparator}` + | ||
`${ArrayPrototypeJoin(ArrayPrototypeMap(coverages, (coverage, j) => getCell(NumberPrototypeToFixed(coverage, 2), columnPadLengths[j], StringPrototypePadStart, false, coverage)), kSeparator)}${kSeparator}` + | ||
`${getCell(formatUncoveredLines(file.uncoveredLineNumbers, table), uncoveredLinesPadLength, false, truncateEnd)}\n`; | ||
} | ||
|
||
const { totals } = summary; | ||
report += `${pad}${symbol}all files | ` + | ||
`${coverageThreshold(totals.coveredLinePercent, color)} | ` + | ||
`${coverageThreshold(totals.coveredBranchPercent, color)} | ` + | ||
`${coverageThreshold(totals.coveredFunctionPercent, color)} |\n`; | ||
// Foot | ||
if (table) report += addTableLine(prefix, tableWidth); | ||
report += `${prefix}${getCell('all files', filePadLength, StringPrototypePadEnd, truncateEnd)}${kSeparator}` + | ||
`${ArrayPrototypeJoin(ArrayPrototypeMap(kColumnsKeys, (columnKey, j) => getCell(NumberPrototypeToFixed(summary.totals[columnKey], 2), columnPadLengths[j], StringPrototypePadStart, false, summary.totals[columnKey])), kSeparator)} |\n`; | ||
if (table) report += addTableLine(prefix, tableWidth); | ||
|
||
report += `${pad}${symbol}end of coverage report\n`; | ||
report += `${prefix}end of coverage report\n`; | ||
if (color) { | ||
report += white; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use
.at(-1)
?