Skip to content

Commit 1daec9a

Browse files
pmarchinitargos
authored andcommitted
test_runner: avoid coverage report partial file names
Co-author: Medhansh404 <21ucs126@lnmiit.ac.in> PR-URL: #54379 Fixes: #51299 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
1 parent c8b7a64 commit 1daec9a

19 files changed

+544
-156
lines changed

lib/internal/test_runner/utils.js

+118-37
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
'use strict';
22
const {
33
ArrayPrototypeFlatMap,
4+
ArrayPrototypeForEach,
45
ArrayPrototypeJoin,
56
ArrayPrototypeMap,
7+
ArrayPrototypePop,
68
ArrayPrototypePush,
79
ArrayPrototypeReduce,
810
ArrayPrototypeSome,
@@ -24,7 +26,7 @@ const {
2426
} = primordials;
2527

2628
const { AsyncResource } = require('async_hooks');
27-
const { relative } = require('path');
29+
const { relative, sep } = require('path');
2830
const { createWriteStream } = require('fs');
2931
const { pathToFileURL } = require('internal/url');
3032
const { createDeferredPromise } = require('internal/util');
@@ -409,6 +411,36 @@ const kColumns = ['line %', 'branch %', 'funcs %'];
409411
const kColumnsKeys = ['coveredLinePercent', 'coveredBranchPercent', 'coveredFunctionPercent'];
410412
const kSeparator = ' | ';
411413

414+
function buildFileTree(summary) {
415+
const tree = { __proto__: null };
416+
let treeDepth = 1;
417+
let longestFile = 0;
418+
419+
ArrayPrototypeForEach(summary.files, (file) => {
420+
let longestPart = 0;
421+
const parts = StringPrototypeSplit(relative(summary.workingDirectory, file.path), sep);
422+
let current = tree;
423+
424+
ArrayPrototypeForEach(parts, (part, index) => {
425+
if (!current[part]) {
426+
current[part] = { __proto__: null };
427+
}
428+
current = current[part];
429+
// If this is the last part, add the file to the tree
430+
if (index === parts.length - 1) {
431+
current.file = file;
432+
}
433+
// Keep track of the longest part for padding
434+
longestPart = MathMax(longestPart, part.length);
435+
});
436+
437+
treeDepth = MathMax(treeDepth, parts.length);
438+
longestFile = MathMax(longestPart, longestFile);
439+
});
440+
441+
return { __proto__: null, tree, treeDepth, longestFile };
442+
}
443+
412444
function getCoverageReport(pad, summary, symbol, color, table) {
413445
const prefix = `${pad}${symbol}`;
414446
let report = `${color}${prefix}start of coverage report\n`;
@@ -418,11 +450,19 @@ function getCoverageReport(pad, summary, symbol, color, table) {
418450
let uncoveredLinesPadLength;
419451
let tableWidth;
420452

453+
// Create a tree of file paths
454+
const { tree, treeDepth, longestFile } = buildFileTree(summary);
421455
if (table) {
422-
// Get expected column sizes
423-
filePadLength = table && ArrayPrototypeReduce(summary.files, (acc, file) =>
424-
MathMax(acc, relative(summary.workingDirectory, file.path).length), 0);
456+
// Calculate expected column sizes based on the tree
457+
filePadLength = table && longestFile;
458+
filePadLength += (treeDepth - 1);
459+
if (color) {
460+
filePadLength += 2;
461+
}
425462
filePadLength = MathMax(filePadLength, 'file'.length);
463+
if (filePadLength > (process.stdout.columns / 2)) {
464+
filePadLength = MathFloor(process.stdout.columns / 2);
465+
}
426466
const fileWidth = filePadLength + 2;
427467

428468
columnPadLengths = ArrayPrototypeMap(kColumns, (column) => (table ? MathMax(column.length, 6) : 0));
@@ -435,26 +475,17 @@ function getCoverageReport(pad, summary, symbol, color, table) {
435475

436476
tableWidth = fileWidth + columnsWidth + uncoveredLinesWidth;
437477

438-
// Fit with sensible defaults
439478
const availableWidth = (process.stdout.columns || Infinity) - prefix.length;
440479
const columnsExtras = tableWidth - availableWidth;
441480
if (table && columnsExtras > 0) {
442-
// Ensure file name is sufficiently visible
443-
const minFilePad = MathMin(8, filePadLength);
444-
filePadLength -= MathFloor(columnsExtras * 0.2);
445-
filePadLength = MathMax(filePadLength, minFilePad);
446-
447-
// Get rest of available space, subtracting margins
481+
filePadLength = MathMin(availableWidth * 0.5, filePadLength);
448482
uncoveredLinesPadLength = MathMax(availableWidth - columnsWidth - (filePadLength + 2) - 2, 1);
449-
450-
// Update table width
451483
tableWidth = availableWidth;
452484
} else {
453485
uncoveredLinesPadLength = Infinity;
454486
}
455487
}
456488

457-
458489
function getCell(string, width, pad, truncate, coverage) {
459490
if (!table) return string;
460491

@@ -469,35 +500,85 @@ function getCoverageReport(pad, summary, symbol, color, table) {
469500
return result;
470501
}
471502

472-
// Head
473-
if (table) report += addTableLine(prefix, tableWidth);
474-
report += `${prefix}${getCell('file', filePadLength, StringPrototypePadEnd, truncateEnd)}${kSeparator}` +
475-
`${ArrayPrototypeJoin(ArrayPrototypeMap(kColumns, (column, i) => getCell(column, columnPadLengths[i], StringPrototypePadStart)), kSeparator)}${kSeparator}` +
476-
`${getCell('uncovered lines', uncoveredLinesPadLength, false, truncateEnd)}\n`;
477-
if (table) report += addTableLine(prefix, tableWidth);
503+
function writeReportLine({ file, depth = 0, coveragesColumns, fileCoverage, uncoveredLines }) {
504+
const fileColumn = `${prefix}${StringPrototypeRepeat(' ', depth)}${getCell(file, filePadLength - depth, StringPrototypePadEnd, truncateStart, fileCoverage)}`;
505+
const coverageColumns = ArrayPrototypeJoin(ArrayPrototypeMap(coveragesColumns, (coverage, j) => {
506+
const coverageText = typeof coverage === 'number' ? NumberPrototypeToFixed(coverage, 2) : coverage;
507+
return getCell(coverageText, columnPadLengths[j], StringPrototypePadStart, false, coverage);
508+
}), kSeparator);
478509

479-
// Body
480-
for (let i = 0; i < summary.files.length; ++i) {
481-
const file = summary.files[i];
482-
const relativePath = relative(summary.workingDirectory, file.path);
510+
const uncoveredLinesColumn = getCell(uncoveredLines, uncoveredLinesPadLength, false, truncateEnd);
483511

484-
let fileCoverage = 0;
485-
const coverages = ArrayPrototypeMap(kColumnsKeys, (columnKey) => {
486-
const percent = file[columnKey];
487-
fileCoverage += percent;
488-
return percent;
489-
});
490-
fileCoverage /= kColumnsKeys.length;
512+
return `${fileColumn}${kSeparator}${coverageColumns}${kSeparator}${uncoveredLinesColumn}\n`;
513+
}
491514

492-
report += `${prefix}${getCell(relativePath, filePadLength, StringPrototypePadEnd, truncateStart, fileCoverage)}${kSeparator}` +
493-
`${ArrayPrototypeJoin(ArrayPrototypeMap(coverages, (coverage, j) => getCell(NumberPrototypeToFixed(coverage, 2), columnPadLengths[j], StringPrototypePadStart, false, coverage)), kSeparator)}${kSeparator}` +
494-
`${getCell(formatUncoveredLines(getUncoveredLines(file.lines), table), uncoveredLinesPadLength, false, truncateEnd)}\n`;
515+
function printCoverageBodyTree(tree, depth = 0) {
516+
for (const key in tree) {
517+
if (tree[key].file) {
518+
const file = tree[key].file;
519+
const fileName = ArrayPrototypePop(StringPrototypeSplit(file.path, sep));
520+
521+
let fileCoverage = 0;
522+
const coverages = ArrayPrototypeMap(kColumnsKeys, (columnKey) => {
523+
const percent = file[columnKey];
524+
fileCoverage += percent;
525+
return percent;
526+
});
527+
fileCoverage /= kColumnsKeys.length;
528+
529+
const uncoveredLines = formatUncoveredLines(getUncoveredLines(file.lines), table);
530+
531+
report += writeReportLine({
532+
__proto__: null,
533+
file: fileName,
534+
depth: depth,
535+
coveragesColumns: coverages,
536+
fileCoverage: fileCoverage,
537+
uncoveredLines: uncoveredLines,
538+
});
539+
} else {
540+
report += writeReportLine({
541+
__proto__: null,
542+
file: key,
543+
depth: depth,
544+
coveragesColumns: ArrayPrototypeMap(columnPadLengths, () => ''),
545+
fileCoverage: undefined,
546+
uncoveredLines: '',
547+
});
548+
printCoverageBodyTree(tree[key], depth + 1);
549+
}
550+
}
495551
}
496552

497-
// Foot
553+
// -------------------------- Coverage Report --------------------------
554+
if (table) report += addTableLine(prefix, tableWidth);
555+
556+
// Print the header
557+
report += writeReportLine({
558+
__proto__: null,
559+
file: 'file',
560+
coveragesColumns: kColumns,
561+
fileCoverage: undefined,
562+
uncoveredLines: 'uncovered lines',
563+
});
564+
565+
if (table) report += addTableLine(prefix, tableWidth);
566+
567+
// Print the body
568+
printCoverageBodyTree(tree);
569+
498570
if (table) report += addTableLine(prefix, tableWidth);
499-
report += `${prefix}${getCell('all files', filePadLength, StringPrototypePadEnd, truncateEnd)}${kSeparator}` +
500-
`${ArrayPrototypeJoin(ArrayPrototypeMap(kColumnsKeys, (columnKey, j) => getCell(NumberPrototypeToFixed(summary.totals[columnKey], 2), columnPadLengths[j], StringPrototypePadStart, false, summary.totals[columnKey])), kSeparator)} |\n`;
571+
572+
// Print the footer
573+
const allFilesCoverages = ArrayPrototypeMap(kColumnsKeys, (columnKey) => summary.totals[columnKey]);
574+
report += writeReportLine({
575+
__proto__: null,
576+
file: 'all files',
577+
coveragesColumns: allFilesCoverages,
578+
fileCoverage: undefined,
579+
uncoveredLines: '',
580+
});
581+
501582
if (table) report += addTableLine(prefix, tableWidth);
502583

503584
report += `${prefix}end of coverage report\n`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use strict';
2+
// Here we can't import common module as the coverage will be different based on the system
3+
4+
// Empty functions that don't do anything
5+
function doNothing1() {
6+
// Not implemented
7+
}
8+
9+
function doNothing2() {
10+
// No logic here
11+
}
12+
13+
function unusedFunction1() {
14+
// Intentionally left empty
15+
}
16+
17+
function unusedFunction2() {
18+
// Another empty function
19+
}
20+
21+
// Unused variables
22+
const unusedVariable1 = 'This is never used';
23+
const unusedVariable2 = 42;
24+
let unusedVariable3;
25+
26+
// Empty class with no methods
27+
class UnusedClass {
28+
constructor() {
29+
// Constructor does nothing
30+
}
31+
}
32+
33+
// Empty object literal
34+
const emptyObject = {};
35+
36+
// Empty array
37+
const emptyArray = [];
38+
39+
// Function with parameters but no body
40+
function doNothingWithParams(param1, param2) {
41+
// No implementation
42+
}
43+
44+
// Function that returns nothing
45+
function returnsNothing() {
46+
// No return statement
47+
}
48+
49+
// Another unused function
50+
function unusedFunction3() {
51+
// More empty code
52+
}

test/fixtures/test-runner/output/coverage-width-100-uncovered-lines.snapshot

+11-6
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@ ok 1 - Coverage Print Fixed Width 100
1515
# duration_ms *
1616
# start of coverage report
1717
# --------------------------------------------------------------------------------------------------
18-
# file | line % | branch % | funcs % | uncovered lines
18+
# file | line % | branch % | funcs % | uncovered lines
1919
# --------------------------------------------------------------------------------------------------
20-
# …ap/a.js | 55.77 | 100.00 | 0.00 | 5-7 9-11 13-15 17-19 29-30 40-42 45-47 50-52
21-
# …ap/b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
22-
# …ines.js | 50.99 | 42.86 | 1.92 | 5-7 9-11 13-15 17-19 29-30 40-42 45-47 50-52 55-57 59-6…
23-
# …nes.mjs | 100.00 | 100.00 | 100.00 |
20+
# test | | | |
21+
# fixtures | | | |
22+
# test-runner | | | |
23+
# coverage-snap | | | |
24+
# a.js | 55.77 | 100.00 | 0.00 | 5-7 9-11 13-15 17-19 …
25+
# b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
26+
# many-uncovered-lines.js | 50.99 | 42.86 | 1.92 | 5-7 9-11 13-15 17-19 …
27+
# output | | | |
28+
# coverage-width-100-uncovered-lines.mjs | 100.00 | 100.00 | 100.00 |
2429
# --------------------------------------------------------------------------------------------------
25-
# all fil… | 52.80 | 60.00 | 1.61 |
30+
# all files | 52.80 | 60.00 | 1.61 |
2631
# --------------------------------------------------------------------------------------------------
2732
# end of coverage report

test/fixtures/test-runner/output/coverage-width-100.snapshot

+10-5
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@ ok 1 - Coverage Print Fixed Width 100
1515
# duration_ms *
1616
# start of coverage report
1717
# --------------------------------------------------------------------------------------------------
18-
# file | line % | branch % | funcs % | uncovered lines
18+
# file | line % | branch % | funcs % | uncovered lines
1919
# --------------------------------------------------------------------------------------------------
20-
# test/fixtures/test-runner/coverage-snap/a.js | 55.77 | 100.00 | 0.00 | 5-7 9-11 13-15 …
21-
# test/fixtures/test-runner/coverage-snap/b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
22-
# …tures/test-runner/output/coverage-width-100.mjs | 100.00 | 100.00 | 100.00 |
20+
# test | | | |
21+
# fixtures | | | |
22+
# test-runner | | | |
23+
# coverage-snap | | | |
24+
# a.js | 55.77 | 100.00 | 0.00 | 5-7 9-11 13-15 17-19 29-30 40-42 45-4…
25+
# b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
26+
# output | | | |
27+
# coverage-width-100.mjs | 100.00 | 100.00 | 100.00 |
2328
# --------------------------------------------------------------------------------------------------
24-
# all files | 60.81 | 100.00 | 0.00 |
29+
# all files | 60.81 | 100.00 | 0.00 |
2530
# --------------------------------------------------------------------------------------------------
2631
# end of coverage report

test/fixtures/test-runner/output/coverage-width-150-uncovered-lines.snapshot

+11-6
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@ ok 1 - Coverage Print Fixed Width 150
1515
# duration_ms *
1616
# start of coverage report
1717
# ----------------------------------------------------------------------------------------------------------------------------------------------------
18-
# file | line % | branch % | funcs % | uncovered lines
18+
# file | line % | branch % | funcs % | uncovered lines
1919
# ----------------------------------------------------------------------------------------------------------------------------------------------------
20-
# …ap/a.js | 55.77 | 100.00 | 0.00 | 5-7 9-11 13-15 17-19 29-30 40-42 45-47 50-52
21-
# …ap/b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
22-
# …ines.js | 50.99 | 42.86 | 1.92 | 5-7 9-11 13-15 17-19 29-30 40-42 45-47 50-52 55-57 59-61 63-65 67-69 91-93 96-98 100-102 104-106 111-112 …
23-
# …nes.mjs | 100.00 | 100.00 | 100.00 |
20+
# test | | | |
21+
# fixtures | | | |
22+
# test-runner | | | |
23+
# coverage-snap | | | |
24+
# a.js | 55.77 | 100.00 | 0.00 | 5-7 9-11 13-15 17-19 29-30 40-42 45-47 50-52
25+
# b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
26+
# many-uncovered-lines.js | 50.99 | 42.86 | 1.92 | 5-7 9-11 13-15 17-19 29-30 40-42 45-47 50-52 55-57 59-61 63-65 67-69 91…
27+
# output | | | |
28+
# coverage-width-150-uncovered-lines.mjs | 100.00 | 100.00 | 100.00 |
2429
# ----------------------------------------------------------------------------------------------------------------------------------------------------
25-
# all fil… | 52.80 | 60.00 | 1.61 |
30+
# all files | 52.80 | 60.00 | 1.61 |
2631
# ----------------------------------------------------------------------------------------------------------------------------------------------------
2732
# end of coverage report

test/fixtures/test-runner/output/coverage-width-150.snapshot

+14-9
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@ ok 1 - Coverage Print Fixed Width 150
1414
# todo 0
1515
# duration_ms *
1616
# start of coverage report
17-
# -------------------------------------------------------------------------------------------------------------------------------------
18-
# file | line % | branch % | funcs % | uncovered lines
19-
# -------------------------------------------------------------------------------------------------------------------------------------
20-
# test/fixtures/test-runner/coverage-snap/a.js | 55.77 | 100.00 | 0.00 | 5-7 9-11 13-15 17-19 29-30 40-42 45-47 50-52
21-
# test/fixtures/test-runner/coverage-snap/b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
22-
# test/fixtures/test-runner/output/coverage-width-150.mjs | 100.00 | 100.00 | 100.00 |
23-
# -------------------------------------------------------------------------------------------------------------------------------------
24-
# all files | 60.81 | 100.00 | 0.00 |
25-
# -------------------------------------------------------------------------------------------------------------------------------------
17+
# --------------------------------------------------------------------------------------------------------
18+
# file | line % | branch % | funcs % | uncovered lines
19+
# --------------------------------------------------------------------------------------------------------
20+
# test | | | |
21+
# fixtures | | | |
22+
# test-runner | | | |
23+
# coverage-snap | | | |
24+
# a.js | 55.77 | 100.00 | 0.00 | 5-7 9-11 13-15 17-19 29-30 40-42 45-47 50-52
25+
# b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
26+
# output | | | |
27+
# coverage-width-150.mjs | 100.00 | 100.00 | 100.00 |
28+
# --------------------------------------------------------------------------------------------------------
29+
# all files | 60.81 | 100.00 | 0.00 |
30+
# --------------------------------------------------------------------------------------------------------
2631
# end of coverage report
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Flags: --experimental-test-coverage
2+
// here we can't import common module as the coverage will be different based on the system
3+
// Unused imports are here in order to populate the coverage report
4+
import * as a from '../coverage-snap/b.js';
5+
import * as b from '../coverage-snap/a.js';
6+
import * as c from '../coverage-snap/a-very-long-long-long-sub-dir/c.js';
7+
8+
import { test } from 'node:test';
9+
10+
process.stdout.columns = 40;
11+
12+
test(`Coverage Print Fixed Width ${process.stdout.columns}`);

0 commit comments

Comments
 (0)