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 f302286

Browse filesBrowse files
dmnsgnruyadorno
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 bf1525c commit f302286
Copy full SHA for f302286

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,21 +3,29 @@ const {
33
ArrayPrototypeJoin,
44
ArrayPrototypeMap,
55
ArrayPrototypePush,
6+
ArrayPrototypeReduce,
67
ObjectCreate,
78
ObjectGetOwnPropertyDescriptor,
9+
MathFloor,
10+
MathMax,
11+
MathMin,
812
NumberPrototypeToFixed,
913
SafePromiseAllReturnArrayLike,
1014
RegExp,
1115
RegExpPrototypeExec,
1216
SafeMap,
17+
StringPrototypePadStart,
18+
StringPrototypePadEnd,
19+
StringPrototypeRepeat,
20+
StringPrototypeSlice,
1321
} = primordials;
1422

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

2230
const {
2331
codes: {
@@ -28,6 +36,13 @@ const {
2836
} = require('internal/errors');
2937
const { compose } = require('stream');
3038

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

259274

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

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

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

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

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

298-
report += `${pad}${symbol}end of coverage report\n`;
407+
report += `${prefix}end of coverage report\n`;
299408
if (color) {
300409
report += white;
301410
}
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.