Skip to content
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: avoid coverage report partial file names #54379

Merged
merged 13 commits into from
Sep 18, 2024
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
Loading