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 99312a5

Browse filesBrowse files
pulkit-30MoLow
authored andcommitted
test_runner: add code coverage support to spec reporter
PR-URL: #46674 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
1 parent dc22edb commit 99312a5
Copy full SHA for 99312a5

File tree

Expand file treeCollapse file tree

4 files changed

+163
-89
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+163
-89
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
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const assert = require('assert');
1515
const Transform = require('internal/streams/transform');
1616
const { inspectWithNoCustomRetry } = require('internal/errors');
1717
const { green, blue, red, white, gray } = require('internal/util/colors');
18-
18+
const { getCoverageReport } = require('internal/test_runner/utils');
1919

2020
const inspectOptions = { __proto__: null, colors: true, breakLength: Infinity };
2121

@@ -30,6 +30,7 @@ const symbols = {
3030
'test:fail': '\u2716 ',
3131
'test:pass': '\u2714 ',
3232
'test:diagnostic': '\u2139 ',
33+
'test:coverage': '\u2139 ',
3334
'arrow:right': '\u25B6 ',
3435
'hyphen:minus': '\uFE63 ',
3536
};
@@ -115,6 +116,8 @@ class SpecReporter extends Transform {
115116
break;
116117
case 'test:diagnostic':
117118
return `${colors[type]}${this.#indent(data.nesting)}${symbols[type]}${data.message}${white}\n`;
119+
case 'test:coverage':
120+
return getCoverageReport(this.#indent(data.nesting), data.summary, symbols['test:coverage'], blue);
118121
}
119122
}
120123
_transform({ type, data }, encoding, callback) {
Collapse file

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

Copy file name to clipboardExpand all lines: lib/internal/test_runner/reporter/tap.js
+2-37Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ const {
33
ArrayPrototypeForEach,
44
ArrayPrototypeJoin,
55
ArrayPrototypePush,
6-
NumberPrototypeToFixed,
76
ObjectEntries,
87
RegExpPrototypeSymbolReplace,
98
SafeMap,
@@ -13,7 +12,7 @@ const {
1312
} = primordials;
1413
const { inspectWithNoCustomRetry } = require('internal/errors');
1514
const { isError, kEmptyObject } = require('internal/util');
16-
const { relative } = require('path');
15+
const { getCoverageReport } = require('internal/test_runner/utils');
1716
const kDefaultIndent = ' '; // 4 spaces
1817
const kFrameStartRegExp = /^ {4}at /;
1918
const kLineBreakRegExp = /\n|\r\n/;
@@ -49,7 +48,7 @@ async function * tapReporter(source) {
4948
yield `${indent(data.nesting)}# ${tapEscape(data.message)}\n`;
5049
break;
5150
case 'test:coverage':
52-
yield reportCoverage(data.nesting, data.summary);
51+
yield getCoverageReport(indent(data.nesting), data.summary, '# ', '');
5352
break;
5453
}
5554
}
@@ -73,40 +72,6 @@ function reportTest(nesting, testNumber, status, name, skip, todo) {
7372
return line;
7473
}
7574

76-
function reportCoverage(nesting, summary) {
77-
const pad = indent(nesting);
78-
let report = `${pad}# start of coverage report\n`;
79-
80-
report += `${pad}# file | line % | branch % | funcs % | uncovered lines\n`;
81-
82-
for (let i = 0; i < summary.files.length; ++i) {
83-
const {
84-
path,
85-
coveredLinePercent,
86-
coveredBranchPercent,
87-
coveredFunctionPercent,
88-
uncoveredLineNumbers,
89-
} = summary.files[i];
90-
const relativePath = relative(summary.workingDirectory, path);
91-
const lines = NumberPrototypeToFixed(coveredLinePercent, 2);
92-
const branches = NumberPrototypeToFixed(coveredBranchPercent, 2);
93-
const functions = NumberPrototypeToFixed(coveredFunctionPercent, 2);
94-
const uncovered = ArrayPrototypeJoin(uncoveredLineNumbers, ', ');
95-
96-
report += `${pad}# ${relativePath} | ${lines} | ${branches} | ` +
97-
`${functions} | ${uncovered}\n`;
98-
}
99-
100-
const { totals } = summary;
101-
report += `${pad}# all files | ` +
102-
`${NumberPrototypeToFixed(totals.coveredLinePercent, 2)} | ` +
103-
`${NumberPrototypeToFixed(totals.coveredBranchPercent, 2)} | ` +
104-
`${NumberPrototypeToFixed(totals.coveredFunctionPercent, 2)} |\n`;
105-
106-
report += `${pad}# end of coverage report\n`;
107-
return report;
108-
}
109-
11075
function reportDetails(nesting, data = kEmptyObject) {
11176
const { error, duration_ms } = data;
11277
const _indent = indent(nesting);
Collapse file

‎lib/internal/test_runner/utils.js‎

Copy file name to clipboardExpand all lines: lib/internal/test_runner/utils.js
+51-1Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
'use strict';
22
const {
3+
ArrayPrototypeJoin,
34
ArrayPrototypeMap,
45
ArrayPrototypePush,
56
ObjectCreate,
67
ObjectGetOwnPropertyDescriptor,
8+
NumberPrototypeToFixed,
79
SafePromiseAllReturnArrayLike,
810
RegExp,
911
RegExpPrototypeExec,
1012
SafeMap,
1113
} = primordials;
1214

13-
const { basename } = require('path');
15+
const { basename, relative } = require('path');
1416
const { createWriteStream } = require('fs');
1517
const { pathToFileURL } = require('internal/url');
1618
const { createDeferredPromise } = require('internal/util');
1719
const { getOptionValue } = require('internal/options');
20+
const { green, red, white } = require('internal/util/colors');
1821

1922
const {
2023
codes: {
@@ -247,6 +250,52 @@ function countCompletedTest(test, harness = test.root.harness) {
247250
harness.counters.all++;
248251
}
249252

253+
254+
function coverageThreshold(coverage, color) {
255+
coverage = NumberPrototypeToFixed(coverage, 2);
256+
if (color) {
257+
if (coverage > 90) return `${green}${coverage}${color}`;
258+
if (coverage < 50) return `${red}${coverage}${color}`;
259+
}
260+
return coverage;
261+
}
262+
263+
function getCoverageReport(pad, summary, symbol, color) {
264+
let report = `${color}${pad}${symbol}start of coverage report\n`;
265+
266+
report += `${pad}${symbol}file | line % | branch % | funcs % | uncovered lines\n`;
267+
268+
for (let i = 0; i < summary.files.length; ++i) {
269+
const {
270+
path,
271+
coveredLinePercent,
272+
coveredBranchPercent,
273+
coveredFunctionPercent,
274+
uncoveredLineNumbers,
275+
} = summary.files[i];
276+
const relativePath = relative(summary.workingDirectory, path);
277+
const lines = coverageThreshold(coveredLinePercent, color);
278+
const branches = coverageThreshold(coveredBranchPercent, color);
279+
const functions = coverageThreshold(coveredFunctionPercent, color);
280+
const uncovered = ArrayPrototypeJoin(uncoveredLineNumbers, ', ');
281+
282+
report += `${pad}${symbol}${relativePath} | ${lines} | ${branches} | ` +
283+
`${functions} | ${uncovered}\n`;
284+
}
285+
286+
const { totals } = summary;
287+
report += `${pad}${symbol}all files | ` +
288+
`${coverageThreshold(totals.coveredLinePercent, color)} | ` +
289+
`${coverageThreshold(totals.coveredBranchPercent, color)} | ` +
290+
`${coverageThreshold(totals.coveredFunctionPercent, color)} |\n`;
291+
292+
report += `${pad}${symbol}end of coverage report\n`;
293+
if (color) {
294+
report += white;
295+
}
296+
return report;
297+
}
298+
250299
module.exports = {
251300
convertStringToRegExp,
252301
countCompletedTest,
@@ -256,4 +305,5 @@ module.exports = {
256305
isTestFailureError,
257306
parseCommandLine,
258307
setupTestReporters,
308+
getCoverageReport,
259309
};
Collapse file

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

Copy file name to clipboardExpand all lines: test/parallel/test-runner-coverage.js
+106-50Lines changed: 106 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function findCoverageFileForPid(pid) {
1818
});
1919
}
2020

21-
function getCoverageFixtureReport() {
21+
function getTapCoverageFixtureReport() {
2222
const report = [
2323
'# start of coverage report',
2424
'# file | line % | branch % | funcs % | uncovered lines',
@@ -37,64 +37,120 @@ function getCoverageFixtureReport() {
3737
return report;
3838
}
3939

40-
test('--experimental-test-coverage and --test cannot be combined', () => {
41-
// TODO(cjihrig): This test can be removed once multi-process code coverage
42-
// is supported.
43-
const args = ['--test', '--experimental-test-coverage'];
44-
const result = spawnSync(process.execPath, args);
45-
46-
// 9 is the documented exit code for an invalid CLI argument.
47-
assert.strictEqual(result.status, 9);
48-
assert.match(
49-
result.stderr.toString(),
50-
/--experimental-test-coverage cannot be used with --test/
51-
);
52-
});
40+
function getSpecCoverageFixtureReport() {
41+
const report = [
42+
'\u2139 start of coverage report',
43+
'\u2139 file | line % | branch % | funcs % | uncovered lines',
44+
'\u2139 test/fixtures/test-runner/coverage.js | 78.65 | 38.46 | 60.00 | 12, ' +
45+
'13, 16, 17, 18, 19, 20, 21, 22, 27, 39, 43, 44, 61, 62, 66, 67, 71, 72',
46+
'\u2139 test/fixtures/test-runner/invalid-tap.js | 100.00 | 100.00 | 100.00 | ',
47+
'\u2139 test/fixtures/v8-coverage/throw.js | 71.43 | 50.00 | 100.00 | 5, 6',
48+
'\u2139 all files | 78.35 | 43.75 | 60.00 |',
49+
'\u2139 end of coverage report',
50+
].join('\n');
5351

54-
test('handles the inspector not being available', (t) => {
55-
if (process.features.inspector) {
56-
return;
52+
if (common.isWindows) {
53+
return report.replaceAll('/', '\\');
5754
}
5855

59-
const fixture = fixtures.path('test-runner', 'coverage.js');
60-
const args = ['--experimental-test-coverage', fixture];
61-
const result = spawnSync(process.execPath, args);
56+
return report;
57+
}
6258

63-
assert(!result.stdout.toString().includes('# start of coverage report'));
64-
assert(result.stderr.toString().includes('coverage could not be collected'));
65-
assert.strictEqual(result.status, 0);
66-
assert(!findCoverageFileForPid(result.pid));
67-
});
59+
test('test coverage report', async (t) => {
60+
await t.test('--experimental-test-coverage and --test cannot be combined', () => {
61+
// TODO(cjihrig): This test can be removed once multi-process code coverage
62+
// is supported.
63+
const args = ['--test', '--experimental-test-coverage'];
64+
const result = spawnSync(process.execPath, args);
65+
66+
// 9 is the documented exit code for an invalid CLI argument.
67+
assert.strictEqual(result.status, 9);
68+
assert.match(
69+
result.stderr.toString(),
70+
/--experimental-test-coverage cannot be used with --test/
71+
);
72+
});
6873

69-
test('coverage is reported and dumped to NODE_V8_COVERAGE if present', (t) => {
70-
if (!process.features.inspector) {
71-
return;
72-
}
74+
await t.test('handles the inspector not being available', (t) => {
75+
if (process.features.inspector) {
76+
return;
77+
}
7378

74-
const fixture = fixtures.path('test-runner', 'coverage.js');
75-
const args = ['--experimental-test-coverage', fixture];
76-
const options = { env: { ...process.env, NODE_V8_COVERAGE: tmpdir.path } };
77-
const result = spawnSync(process.execPath, args, options);
78-
const report = getCoverageFixtureReport();
79+
const fixture = fixtures.path('test-runner', 'coverage.js');
80+
const args = ['--experimental-test-coverage', fixture];
81+
const result = spawnSync(process.execPath, args);
7982

80-
assert(result.stdout.toString().includes(report));
81-
assert.strictEqual(result.stderr.toString(), '');
82-
assert.strictEqual(result.status, 0);
83-
assert(findCoverageFileForPid(result.pid));
83+
assert(!result.stdout.toString().includes('# start of coverage report'));
84+
assert(result.stderr.toString().includes('coverage could not be collected'));
85+
assert.strictEqual(result.status, 0);
86+
assert(!findCoverageFileForPid(result.pid));
87+
});
8488
});
8589

86-
test('coverage is reported without NODE_V8_COVERAGE present', (t) => {
87-
if (!process.features.inspector) {
88-
return;
89-
}
90+
test('test tap coverage reporter', async (t) => {
91+
await t.test('coverage is reported and dumped to NODE_V8_COVERAGE if present', (t) => {
92+
if (!process.features.inspector) {
93+
return;
94+
}
95+
96+
const fixture = fixtures.path('test-runner', 'coverage.js');
97+
const args = ['--experimental-test-coverage', '--test-reporter', 'tap', fixture];
98+
const options = { env: { ...process.env, NODE_V8_COVERAGE: tmpdir.path } };
99+
const result = spawnSync(process.execPath, args, options);
100+
const report = getTapCoverageFixtureReport();
101+
102+
assert(result.stdout.toString().includes(report));
103+
assert.strictEqual(result.stderr.toString(), '');
104+
assert.strictEqual(result.status, 0);
105+
assert(findCoverageFileForPid(result.pid));
106+
});
107+
108+
await t.test('coverage is reported without NODE_V8_COVERAGE present', (t) => {
109+
if (!process.features.inspector) {
110+
return;
111+
}
112+
113+
const fixture = fixtures.path('test-runner', 'coverage.js');
114+
const args = ['--experimental-test-coverage', '--test-reporter', 'tap', fixture];
115+
const result = spawnSync(process.execPath, args);
116+
const report = getTapCoverageFixtureReport();
90117

91-
const fixture = fixtures.path('test-runner', 'coverage.js');
92-
const args = ['--experimental-test-coverage', fixture];
93-
const result = spawnSync(process.execPath, args);
94-
const report = getCoverageFixtureReport();
118+
assert(result.stdout.toString().includes(report));
119+
assert.strictEqual(result.stderr.toString(), '');
120+
assert.strictEqual(result.status, 0);
121+
assert(!findCoverageFileForPid(result.pid));
122+
});
123+
});
95124

96-
assert(result.stdout.toString().includes(report));
97-
assert.strictEqual(result.stderr.toString(), '');
98-
assert.strictEqual(result.status, 0);
99-
assert(!findCoverageFileForPid(result.pid));
125+
test('test spec coverage reporter', async (t) => {
126+
await t.test('coverage is reported and dumped to NODE_V8_COVERAGE if present', (t) => {
127+
if (!process.features.inspector) {
128+
return;
129+
}
130+
const fixture = fixtures.path('test-runner', 'coverage.js');
131+
const args = ['--experimental-test-coverage', '--test-reporter', 'spec', fixture];
132+
const options = { env: { ...process.env, NODE_V8_COVERAGE: tmpdir.path } };
133+
const result = spawnSync(process.execPath, args, options);
134+
const report = getSpecCoverageFixtureReport();
135+
136+
assert(result.stdout.toString().includes(report));
137+
assert.strictEqual(result.stderr.toString(), '');
138+
assert.strictEqual(result.status, 0);
139+
assert(findCoverageFileForPid(result.pid));
140+
});
141+
142+
await t.test('coverage is reported without NODE_V8_COVERAGE present', (t) => {
143+
if (!process.features.inspector) {
144+
return;
145+
}
146+
const fixture = fixtures.path('test-runner', 'coverage.js');
147+
const args = ['--experimental-test-coverage', '--test-reporter', 'spec', fixture];
148+
const result = spawnSync(process.execPath, args);
149+
const report = getSpecCoverageFixtureReport();
150+
151+
assert(result.stdout.toString().includes(report));
152+
assert.strictEqual(result.stderr.toString(), '');
153+
assert.strictEqual(result.status, 0);
154+
assert(!findCoverageFileForPid(result.pid));
155+
});
100156
});

0 commit comments

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