Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 93bf447

Browse filesBrowse files
dmnsgnRafaelGSS
authored andcommitted
test_runner: refactor coverage report output for readability
Add a "table" parameter to getCoverageReport. Keep the tap coverage output intact. Change the output by adding padding and truncating the tables' cells. Add separation lines for table head/body/foot. Group uncovered lines as ranges. Add yellow color for coverage between 50 and 90. Refs: #46674 PR-URL: #47791 Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
1 parent 159ab66 commit 93bf447
Copy full SHA for 93bf447

File tree

Expand file treeCollapse file tree

4 files changed

+153
-38
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+153
-38
lines changed
Open diff view settings
Collapse file

‎lib/internal/test_runner/reporter/spec.js‎

Copy file name to clipboardExpand all lines: lib/internal/test_runner/reporter/spec.js
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class SpecReporter extends Transform {
123123
case 'test:diagnostic':
124124
return `${colors[type]}${this.#indent(data.nesting)}${symbols[type]}${data.message}${white}\n`;
125125
case 'test:coverage':
126-
return getCoverageReport(this.#indent(data.nesting), data.summary, symbols['test:coverage'], blue);
126+
return getCoverageReport(this.#indent(data.nesting), data.summary, symbols['test:coverage'], blue, true);
127127
}
128128
}
129129
_transform({ type, data }, encoding, callback) {
Collapse file

‎lib/internal/test_runner/utils.js‎

Copy file name to clipboardExpand all lines: lib/internal/test_runner/utils.js
+140-31Lines changed: 140 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,28 @@ const {
33
ArrayPrototypeJoin,
44
ArrayPrototypeMap,
55
ArrayPrototypePush,
6+
ArrayPrototypeReduce,
67
ObjectGetOwnPropertyDescriptor,
8+
MathFloor,
9+
MathMax,
10+
MathMin,
711
NumberPrototypeToFixed,
812
SafePromiseAllReturnArrayLike,
913
RegExp,
1014
RegExpPrototypeExec,
1115
SafeMap,
16+
StringPrototypePadStart,
17+
StringPrototypePadEnd,
18+
StringPrototypeRepeat,
19+
StringPrototypeSlice,
1220
} = primordials;
1321

1422
const { basename, relative } = require('path');
1523
const { createWriteStream } = require('fs');
1624
const { pathToFileURL } = require('internal/url');
1725
const { createDeferredPromise } = require('internal/util');
1826
const { getOptionValue } = require('internal/options');
19-
const { green, red, white, shouldColorize } = require('internal/util/colors');
27+
const { green, yellow, red, white, shouldColorize } = require('internal/util/colors');
2028

2129
const {
2230
codes: {
@@ -27,6 +35,13 @@ const {
2735
} = require('internal/errors');
2836
const { compose } = require('stream');
2937

38+
const coverageColors = {
39+
__proto__: null,
40+
high: green,
41+
medium: yellow,
42+
low: red,
43+
};
44+
3045
const kMultipleCallbackInvocations = 'multipleCallbackInvocations';
3146
const kRegExpPattern = /^\/(.*)\/([a-z]*)$/;
3247
const kSupportedFileExtensions = /\.[cm]?js$/;
@@ -256,45 +271,139 @@ function countCompletedTest(test, harness = test.root.harness) {
256271
}
257272

258273

259-
function coverageThreshold(coverage, color) {
260-
coverage = NumberPrototypeToFixed(coverage, 2);
261-
if (color) {
262-
if (coverage > 90) return `${green}${coverage}${color}`;
263-
if (coverage < 50) return `${red}${coverage}${color}`;
274+
const memo = new SafeMap();
275+
function addTableLine(prefix, width) {
276+
const key = `${prefix}-${width}`;
277+
let value = memo.get(key);
278+
if (value === undefined) {
279+
value = `${prefix}${StringPrototypeRepeat('-', width)}\n`;
280+
memo.set(key, value);
264281
}
265-
return coverage;
282+
283+
return value;
284+
}
285+
286+
const kHorizontalEllipsis = '\u2026';
287+
function truncateStart(string, width) {
288+
return string.length > width ? `${kHorizontalEllipsis}${StringPrototypeSlice(string, string.length - width + 1)}` : string;
289+
}
290+
291+
function truncateEnd(string, width) {
292+
return string.length > width ? `${StringPrototypeSlice(string, 0, width - 1)}${kHorizontalEllipsis}` : string;
293+
}
294+
295+
function formatLinesToRanges(values) {
296+
return ArrayPrototypeMap(ArrayPrototypeReduce(values, (prev, current, index, array) => {
297+
if ((index > 0) && ((current - array[index - 1]) === 1)) {
298+
prev[prev.length - 1][1] = current;
299+
} else {
300+
prev.push([current]);
301+
}
302+
return prev;
303+
}, []), (range) => ArrayPrototypeJoin(range, '-'));
304+
}
305+
306+
function formatUncoveredLines(lines, table) {
307+
if (table) return ArrayPrototypeJoin(formatLinesToRanges(lines), ' ');
308+
return ArrayPrototypeJoin(lines, ', ');
266309
}
267310

268-
function getCoverageReport(pad, summary, symbol, color) {
269-
let report = `${color}${pad}${symbol}start of coverage report\n`;
311+
const kColumns = ['line %', 'branch %', 'funcs %'];
312+
const kColumnsKeys = ['coveredLinePercent', 'coveredBranchPercent', 'coveredFunctionPercent'];
313+
const kSeparator = ' | ';
314+
315+
function getCoverageReport(pad, summary, symbol, color, table) {
316+
const prefix = `${pad}${symbol}`;
317+
let report = `${color}${prefix}start of coverage report\n`;
318+
319+
let filePadLength;
320+
let columnPadLengths = [];
321+
let uncoveredLinesPadLength;
322+
let tableWidth;
323+
324+
if (table) {
325+
// Get expected column sizes
326+
filePadLength = table && ArrayPrototypeReduce(summary.files, (acc, file) =>
327+
MathMax(acc, relative(summary.workingDirectory, file.path).length), 0);
328+
filePadLength = MathMax(filePadLength, 'file'.length);
329+
const fileWidth = filePadLength + 2;
330+
331+
columnPadLengths = ArrayPrototypeMap(kColumns, (column) => (table ? MathMax(column.length, 6) : 0));
332+
const columnsWidth = ArrayPrototypeReduce(columnPadLengths, (acc, columnPadLength) => acc + columnPadLength + 3, 0);
333+
334+
uncoveredLinesPadLength = table && ArrayPrototypeReduce(summary.files, (acc, file) =>
335+
MathMax(acc, formatUncoveredLines(file.uncoveredLineNumbers, table).length), 0);
336+
uncoveredLinesPadLength = MathMax(uncoveredLinesPadLength, 'uncovered lines'.length);
337+
const uncoveredLinesWidth = uncoveredLinesPadLength + 2;
338+
339+
tableWidth = fileWidth + columnsWidth + uncoveredLinesWidth;
340+
341+
// Fit with sensible defaults
342+
const availableWidth = (process.stdout.columns || Infinity) - prefix.length;
343+
const columnsExtras = tableWidth - availableWidth;
344+
if (table && columnsExtras > 0) {
345+
// Ensure file name is sufficiently visible
346+
const minFilePad = MathMin(8, filePadLength);
347+
filePadLength -= MathFloor(columnsExtras * 0.2);
348+
filePadLength = MathMax(filePadLength, minFilePad);
349+
350+
// Get rest of available space, subtracting margins
351+
uncoveredLinesPadLength = MathMax(availableWidth - columnsWidth - (filePadLength + 2) - 2, 1);
352+
353+
// Update table width
354+
tableWidth = availableWidth;
355+
} else {
356+
uncoveredLinesPadLength = Infinity;
357+
}
358+
}
359+
360+
361+
function getCell(string, width, pad, truncate, coverage) {
362+
if (!table) return string;
363+
364+
let result = string;
365+
if (pad) result = pad(result, width);
366+
if (truncate) result = truncate(result, width);
367+
if (color && coverage !== undefined) {
368+
if (coverage > 90) return `${coverageColors.high}${result}${color}`;
369+
if (coverage > 50) return `${coverageColors.medium}${result}${color}`;
370+
return `${coverageColors.low}${result}${color}`;
371+
}
372+
return result;
373+
}
270374

271-
report += `${pad}${symbol}file | line % | branch % | funcs % | uncovered lines\n`;
375+
// Head
376+
if (table) report += addTableLine(prefix, tableWidth);
377+
report += `${prefix}${getCell('file', filePadLength, StringPrototypePadEnd, truncateEnd)}${kSeparator}` +
378+
`${ArrayPrototypeJoin(ArrayPrototypeMap(kColumns, (column, i) => getCell(column, columnPadLengths[i], StringPrototypePadStart)), kSeparator)}${kSeparator}` +
379+
`${getCell('uncovered lines', uncoveredLinesPadLength, false, truncateEnd)}\n`;
380+
if (table) report += addTableLine(prefix, tableWidth);
272381

382+
// Body
273383
for (let i = 0; i < summary.files.length; ++i) {
274-
const {
275-
path,
276-
coveredLinePercent,
277-
coveredBranchPercent,
278-
coveredFunctionPercent,
279-
uncoveredLineNumbers,
280-
} = summary.files[i];
281-
const relativePath = relative(summary.workingDirectory, path);
282-
const lines = coverageThreshold(coveredLinePercent, color);
283-
const branches = coverageThreshold(coveredBranchPercent, color);
284-
const functions = coverageThreshold(coveredFunctionPercent, color);
285-
const uncovered = ArrayPrototypeJoin(uncoveredLineNumbers, ', ');
286-
287-
report += `${pad}${symbol}${relativePath} | ${lines} | ${branches} | ` +
288-
`${functions} | ${uncovered}\n`;
384+
const file = summary.files[i];
385+
const relativePath = relative(summary.workingDirectory, file.path);
386+
387+
let fileCoverage = 0;
388+
const coverages = ArrayPrototypeMap(kColumnsKeys, (columnKey) => {
389+
const percent = file[columnKey];
390+
fileCoverage += percent;
391+
return percent;
392+
});
393+
fileCoverage /= kColumnsKeys.length;
394+
395+
report += `${prefix}${getCell(relativePath, filePadLength, StringPrototypePadEnd, truncateStart, fileCoverage)}${kSeparator}` +
396+
`${ArrayPrototypeJoin(ArrayPrototypeMap(coverages, (coverage, j) => getCell(NumberPrototypeToFixed(coverage, 2), columnPadLengths[j], StringPrototypePadStart, false, coverage)), kSeparator)}${kSeparator}` +
397+
`${getCell(formatUncoveredLines(file.uncoveredLineNumbers, table), uncoveredLinesPadLength, false, truncateEnd)}\n`;
289398
}
290399

291-
const { totals } = summary;
292-
report += `${pad}${symbol}all files | ` +
293-
`${coverageThreshold(totals.coveredLinePercent, color)} | ` +
294-
`${coverageThreshold(totals.coveredBranchPercent, color)} | ` +
295-
`${coverageThreshold(totals.coveredFunctionPercent, color)} |\n`;
400+
// Foot
401+
if (table) report += addTableLine(prefix, tableWidth);
402+
report += `${prefix}${getCell('all files', filePadLength, StringPrototypePadEnd, truncateEnd)}${kSeparator}` +
403+
`${ArrayPrototypeJoin(ArrayPrototypeMap(kColumnsKeys, (columnKey, j) => getCell(NumberPrototypeToFixed(summary.totals[columnKey], 2), columnPadLengths[j], StringPrototypePadStart, false, summary.totals[columnKey])), kSeparator)} |\n`;
404+
if (table) report += addTableLine(prefix, tableWidth);
296405

297-
report += `${pad}${symbol}end of coverage report\n`;
406+
report += `${prefix}end of coverage report\n`;
298407
if (color) {
299408
report += white;
300409
}
Collapse file

‎lib/internal/util/colors.js‎

Copy file name to clipboardExpand all lines: lib/internal/util/colors.js
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ module.exports = {
2828
module.exports.blue = hasColors ? '\u001b[34m' : '';
2929
module.exports.green = hasColors ? '\u001b[32m' : '';
3030
module.exports.white = hasColors ? '\u001b[39m' : '';
31+
module.exports.yellow = hasColors ? '\u001b[33m' : '';
3132
module.exports.red = hasColors ? '\u001b[31m' : '';
3233
module.exports.gray = hasColors ? '\u001b[90m' : '';
3334
module.exports.clear = hasColors ? '\u001bc' : '';
Collapse file

‎test/parallel/test-runner-coverage.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-runner-coverage.js
+11-6Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,21 @@ function getTapCoverageFixtureReport() {
4141
}
4242

4343
function getSpecCoverageFixtureReport() {
44+
/* eslint-disable max-len */
4445
const report = [
4546
'\u2139 start of coverage report',
46-
'\u2139 file | line % | branch % | funcs % | uncovered lines',
47-
'\u2139 test/fixtures/test-runner/coverage.js | 78.65 | 38.46 | 60.00 | 12, ' +
48-
'13, 16, 17, 18, 19, 20, 21, 22, 27, 39, 43, 44, 61, 62, 66, 67, 71, 72',
49-
'\u2139 test/fixtures/test-runner/invalid-tap.js | 100.00 | 100.00 | 100.00 | ',
50-
'\u2139 test/fixtures/v8-coverage/throw.js | 71.43 | 50.00 | 100.00 | 5, 6',
51-
'\u2139 all files | 78.35 | 43.75 | 60.00 |',
47+
'\u2139 -------------------------------------------------------------------------------------------------------------------',
48+
'\u2139 file | line % | branch % | funcs % | uncovered lines',
49+
'\u2139 -------------------------------------------------------------------------------------------------------------------',
50+
'\u2139 test/fixtures/test-runner/coverage.js | 78.65 | 38.46 | 60.00 | 12-13 16-22 27 39 43-44 61-62 66-67 71-72',
51+
'\u2139 test/fixtures/test-runner/invalid-tap.js | 100.00 | 100.00 | 100.00 | ',
52+
'\u2139 test/fixtures/v8-coverage/throw.js | 71.43 | 50.00 | 100.00 | 5-6',
53+
'\u2139 -------------------------------------------------------------------------------------------------------------------',
54+
'\u2139 all files | 78.35 | 43.75 | 60.00 |',
55+
'\u2139 -------------------------------------------------------------------------------------------------------------------',
5256
'\u2139 end of coverage report',
5357
].join('\n');
58+
/* eslint-enable max-len */
5459

5560
if (common.isWindows) {
5661
return report.replaceAll('/', '\\');

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.