Skip to content

Commit

Permalink
test_runner: avoid coverage report partial file names
Browse files Browse the repository at this point in the history
Co-author: Medhansh404 <[email protected]>
PR-URL: nodejs#54379
Fixes: nodejs#51299
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Moshe Atlow <[email protected]>
  • Loading branch information
pmarchini authored and tpoisseau committed Nov 21, 2024
1 parent 53aea5b commit a51990a
Show file tree
Hide file tree
Showing 19 changed files with 544 additions and 156 deletions.
155 changes: 118 additions & 37 deletions lib/internal/test_runner/utils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
'use strict';
const {
ArrayPrototypeFlatMap,
ArrayPrototypeForEach,
ArrayPrototypeJoin,
ArrayPrototypeMap,
ArrayPrototypePop,
ArrayPrototypePush,
ArrayPrototypeReduce,
ArrayPrototypeSome,
Expand All @@ -24,7 +26,7 @@ const {
} = primordials;

const { AsyncResource } = require('async_hooks');
const { relative } = require('path');
const { relative, sep } = require('path');
const { createWriteStream } = require('fs');
const { pathToFileURL } = require('internal/url');
const { createDeferredPromise } = require('internal/util');
Expand Down Expand Up @@ -409,6 +411,36 @@ const kColumns = ['line %', 'branch %', 'funcs %'];
const kColumnsKeys = ['coveredLinePercent', 'coveredBranchPercent', 'coveredFunctionPercent'];
const kSeparator = ' | ';

function buildFileTree(summary) {
const tree = { __proto__: null };
let treeDepth = 1;
let longestFile = 0;

ArrayPrototypeForEach(summary.files, (file) => {
let longestPart = 0;
const parts = StringPrototypeSplit(relative(summary.workingDirectory, file.path), sep);
let current = tree;

ArrayPrototypeForEach(parts, (part, index) => {
if (!current[part]) {
current[part] = { __proto__: null };
}
current = current[part];
// If this is the last part, add the file to the tree
if (index === parts.length - 1) {
current.file = file;
}
// Keep track of the longest part for padding
longestPart = MathMax(longestPart, part.length);
});

treeDepth = MathMax(treeDepth, parts.length);
longestFile = MathMax(longestPart, longestFile);
});

return { __proto__: null, tree, treeDepth, longestFile };
}

function getCoverageReport(pad, summary, symbol, color, table) {
const prefix = `${pad}${symbol}`;
let report = `${color}${prefix}start of coverage report\n`;
Expand All @@ -418,11 +450,19 @@ function getCoverageReport(pad, summary, symbol, color, table) {
let uncoveredLinesPadLength;
let tableWidth;

// Create a tree of file paths
const { tree, treeDepth, longestFile } = buildFileTree(summary);
if (table) {
// Get expected column sizes
filePadLength = table && ArrayPrototypeReduce(summary.files, (acc, file) =>
MathMax(acc, relative(summary.workingDirectory, file.path).length), 0);
// Calculate expected column sizes based on the tree
filePadLength = table && longestFile;
filePadLength += (treeDepth - 1);
if (color) {
filePadLength += 2;
}
filePadLength = MathMax(filePadLength, 'file'.length);
if (filePadLength > (process.stdout.columns / 2)) {
filePadLength = MathFloor(process.stdout.columns / 2);
}
const fileWidth = filePadLength + 2;

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

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
filePadLength = MathMin(availableWidth * 0.5, filePadLength);
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;

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

// 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);
function writeReportLine({ file, depth = 0, coveragesColumns, fileCoverage, uncoveredLines }) {
const fileColumn = `${prefix}${StringPrototypeRepeat(' ', depth)}${getCell(file, filePadLength - depth, StringPrototypePadEnd, truncateStart, fileCoverage)}`;
const coverageColumns = ArrayPrototypeJoin(ArrayPrototypeMap(coveragesColumns, (coverage, j) => {
const coverageText = typeof coverage === 'number' ? NumberPrototypeToFixed(coverage, 2) : coverage;
return getCell(coverageText, columnPadLengths[j], StringPrototypePadStart, false, coverage);
}), kSeparator);

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

let fileCoverage = 0;
const coverages = ArrayPrototypeMap(kColumnsKeys, (columnKey) => {
const percent = file[columnKey];
fileCoverage += percent;
return percent;
});
fileCoverage /= kColumnsKeys.length;
return `${fileColumn}${kSeparator}${coverageColumns}${kSeparator}${uncoveredLinesColumn}\n`;
}

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(getUncoveredLines(file.lines), table), uncoveredLinesPadLength, false, truncateEnd)}\n`;
function printCoverageBodyTree(tree, depth = 0) {
for (const key in tree) {
if (tree[key].file) {
const file = tree[key].file;
const fileName = ArrayPrototypePop(StringPrototypeSplit(file.path, sep));

let fileCoverage = 0;
const coverages = ArrayPrototypeMap(kColumnsKeys, (columnKey) => {
const percent = file[columnKey];
fileCoverage += percent;
return percent;
});
fileCoverage /= kColumnsKeys.length;

const uncoveredLines = formatUncoveredLines(getUncoveredLines(file.lines), table);

report += writeReportLine({
__proto__: null,
file: fileName,
depth: depth,
coveragesColumns: coverages,
fileCoverage: fileCoverage,
uncoveredLines: uncoveredLines,
});
} else {
report += writeReportLine({
__proto__: null,
file: key,
depth: depth,
coveragesColumns: ArrayPrototypeMap(columnPadLengths, () => ''),
fileCoverage: undefined,
uncoveredLines: '',
});
printCoverageBodyTree(tree[key], depth + 1);
}
}
}

// Foot
// -------------------------- Coverage Report --------------------------
if (table) report += addTableLine(prefix, tableWidth);

// Print the header
report += writeReportLine({
__proto__: null,
file: 'file',
coveragesColumns: kColumns,
fileCoverage: undefined,
uncoveredLines: 'uncovered lines',
});

if (table) report += addTableLine(prefix, tableWidth);

// Print the body
printCoverageBodyTree(tree);

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`;

// Print the footer
const allFilesCoverages = ArrayPrototypeMap(kColumnsKeys, (columnKey) => summary.totals[columnKey]);
report += writeReportLine({
__proto__: null,
file: 'all files',
coveragesColumns: allFilesCoverages,
fileCoverage: undefined,
uncoveredLines: '',
});

if (table) report += addTableLine(prefix, tableWidth);

report += `${prefix}end of coverage report\n`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use strict';
// Here we can't import common module as the coverage will be different based on the system

// Empty functions that don't do anything
function doNothing1() {
// Not implemented
}

function doNothing2() {
// No logic here
}

function unusedFunction1() {
// Intentionally left empty
}

function unusedFunction2() {
// Another empty function
}

// Unused variables
const unusedVariable1 = 'This is never used';
const unusedVariable2 = 42;
let unusedVariable3;

// Empty class with no methods
class UnusedClass {
constructor() {
// Constructor does nothing
}
}

// Empty object literal
const emptyObject = {};

// Empty array
const emptyArray = [];

// Function with parameters but no body
function doNothingWithParams(param1, param2) {
// No implementation
}

// Function that returns nothing
function returnsNothing() {
// No return statement
}

// Another unused function
function unusedFunction3() {
// More empty code
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ ok 1 - Coverage Print Fixed Width 100
# duration_ms *
# start of coverage report
# --------------------------------------------------------------------------------------------------
# file | line % | branch % | funcs % | uncovered lines
# file | line % | branch % | funcs % | uncovered lines
# --------------------------------------------------------------------------------------------------
# …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
# …ap/b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
# …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…
# …nes.mjs | 100.00 | 100.00 | 100.00 |
# test | | | |
# fixtures | | | |
# test-runner | | | |
# coverage-snap | | | |
# a.js | 55.77 | 100.00 | 0.00 | 5-7 9-11 13-15 17-19 …
# b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
# many-uncovered-lines.js | 50.99 | 42.86 | 1.92 | 5-7 9-11 13-15 17-19 …
# output | | | |
# coverage-width-100-uncovered-lines.mjs | 100.00 | 100.00 | 100.00 |
# --------------------------------------------------------------------------------------------------
# all fil… | 52.80 | 60.00 | 1.61 |
# all files | 52.80 | 60.00 | 1.61 |
# --------------------------------------------------------------------------------------------------
# end of coverage report
15 changes: 10 additions & 5 deletions test/fixtures/test-runner/output/coverage-width-100.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@ ok 1 - Coverage Print Fixed Width 100
# duration_ms *
# start of coverage report
# --------------------------------------------------------------------------------------------------
# file | line % | branch % | funcs % | uncovered lines
# file | line % | branch % | funcs % | uncovered lines
# --------------------------------------------------------------------------------------------------
# test/fixtures/test-runner/coverage-snap/a.js | 55.77 | 100.00 | 0.00 | 5-7 9-11 13-15 …
# test/fixtures/test-runner/coverage-snap/b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
# …tures/test-runner/output/coverage-width-100.mjs | 100.00 | 100.00 | 100.00 |
# 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-4…
# b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
# output | | | |
# coverage-width-100.mjs | 100.00 | 100.00 | 100.00 |
# --------------------------------------------------------------------------------------------------
# all files | 60.81 | 100.00 | 0.00 |
# all files | 60.81 | 100.00 | 0.00 |
# --------------------------------------------------------------------------------------------------
# end of coverage report
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ ok 1 - Coverage Print Fixed Width 150
# duration_ms *
# start of coverage report
# ----------------------------------------------------------------------------------------------------------------------------------------------------
# file | line % | branch % | funcs % | uncovered lines
# file | line % | branch % | funcs % | uncovered lines
# ----------------------------------------------------------------------------------------------------------------------------------------------------
# …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
# …ap/b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
# …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 …
# …nes.mjs | 100.00 | 100.00 | 100.00 |
# 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
# b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
# 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…
# output | | | |
# coverage-width-150-uncovered-lines.mjs | 100.00 | 100.00 | 100.00 |
# ----------------------------------------------------------------------------------------------------------------------------------------------------
# all fil… | 52.80 | 60.00 | 1.61 |
# all files | 52.80 | 60.00 | 1.61 |
# ----------------------------------------------------------------------------------------------------------------------------------------------------
# end of coverage report
23 changes: 14 additions & 9 deletions test/fixtures/test-runner/output/coverage-width-150.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@ ok 1 - Coverage Print Fixed Width 150
# todo 0
# duration_ms *
# start of coverage report
# -------------------------------------------------------------------------------------------------------------------------------------
# file | line % | branch % | funcs % | uncovered lines
# -------------------------------------------------------------------------------------------------------------------------------------
# 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
# test/fixtures/test-runner/coverage-snap/b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
# test/fixtures/test-runner/output/coverage-width-150.mjs | 100.00 | 100.00 | 100.00 |
# -------------------------------------------------------------------------------------------------------------------------------------
# all files | 60.81 | 100.00 | 0.00 |
# -------------------------------------------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------------
# file | line % | branch % | funcs % | uncovered lines
# --------------------------------------------------------------------------------------------------------
# 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
# b.js | 45.45 | 100.00 | 0.00 | 5-7 9-11
# output | | | |
# coverage-width-150.mjs | 100.00 | 100.00 | 100.00 |
# --------------------------------------------------------------------------------------------------------
# all files | 60.81 | 100.00 | 0.00 |
# --------------------------------------------------------------------------------------------------------
# end of coverage report
12 changes: 12 additions & 0 deletions test/fixtures/test-runner/output/coverage-width-40.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Flags: --experimental-test-coverage
// here we can't import common module as the coverage will be different based on the system
// Unused imports are here in order to populate the coverage report
import * as a from '../coverage-snap/b.js';
import * as b from '../coverage-snap/a.js';
import * as c from '../coverage-snap/a-very-long-long-long-sub-dir/c.js';

import { test } from 'node:test';

process.stdout.columns = 40;

test(`Coverage Print Fixed Width ${process.stdout.columns}`);
Loading

0 comments on commit a51990a

Please sign in to comment.