Skip to content

test_runner: add level-based diagnostic handling for reporter #57923

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/api/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -3004,6 +3004,11 @@ defined. The corresponding declaration ordered event is `'test:start'`.
`undefined` if the test was run through the REPL.
* `message` {string} The diagnostic message.
* `nesting` {number} The nesting level of the test.
* `level` {string} The severity level of the diagnostic message.
Possible values are:
* `'info'`: Informational messages.
* `'warn'`: Warnings.
* `'error'`: Errors.

Emitted when [`context.diagnostic`][] is called.
This event is guaranteed to be emitted in the same order as the tests are
Expand Down
6 changes: 4 additions & 2 deletions lib/internal/test_runner/reporter/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,10 @@ class SpecReporter extends Transform {
case 'test:stderr':
case 'test:stdout':
return data.message;
case 'test:diagnostic':
return `${reporterColorMap[type]}${indent(data.nesting)}${reporterUnicodeSymbolMap[type]}${data.message}${colors.white}\n`;
case 'test:diagnostic':{
const diagnosticColor = reporterColorMap[data.level] || reporterColorMap['test:diagnostic'];
return `${diagnosticColor}${indent(data.nesting)}${reporterUnicodeSymbolMap[type]}${data.message}${colors.white}\n`;
}
case 'test:coverage':
return getCoverageReport(indent(data.nesting), data.summary,
reporterUnicodeSymbolMap['test:coverage'], colors.blue, true);
Expand Down
9 changes: 9 additions & 0 deletions lib/internal/test_runner/reporter/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ const reporterColorMap = {
get 'test:diagnostic'() {
return colors.blue;
},
get 'info'() {
return colors.blue;
},
get 'warn'() {
return colors.yellow;
},
get 'error'() {
return colors.red;
},
};

function indent(nesting) {
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,7 @@ class Test extends AsyncResource {
if (actual < threshold) {
harness.success = false;
process.exitCode = kGenericUserError;
reporter.diagnostic(nesting, loc, `Error: ${NumberPrototypeToFixed(actual, 2)}% ${name} coverage does not meet threshold of ${threshold}%.`);
reporter.diagnostic(nesting, loc, `Error: ${NumberPrototypeToFixed(actual, 2)}% ${name} coverage does not meet threshold of ${threshold}%.`, 'error');
}
}

Expand Down
3 changes: 2 additions & 1 deletion lib/internal/test_runner/tests_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,12 @@ class TestsStream extends Readable {
});
}

diagnostic(nesting, loc, message) {
diagnostic(nesting, loc, message, level = 'info') {
this[kEmitMessage]('test:diagnostic', {
__proto__: null,
nesting,
message,
level,
...loc,
});
}
Expand Down
20 changes: 20 additions & 0 deletions test/parallel/test-runner-coverage-thresholds.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,26 @@ for (const coverage of coverages) {
assert(!findCoverageFileForPid(result.pid));
});

test(`test failing ${coverage.flag} with red color`, () => {
const result = spawnSync(process.execPath, [
'--test',
'--experimental-test-coverage',
'--test-coverage-exclude=!test/**',
`${coverage.flag}=99`,
'--test-reporter', 'spec',
fixture,
], {
env: { ...process.env, FORCE_COLOR: '3' },
});

const stdout = result.stdout.toString();
// eslint-disable-next-line no-control-regex
const redColorRegex = /\u001b\[31mℹ Error: \d{2}\.\d{2}% \w+ coverage does not meet threshold of 99%/;
assert.match(stdout, redColorRegex, 'Expected red color code not found in diagnostic message');
assert.strictEqual(result.status, 1);
assert(!findCoverageFileForPid(result.pid));
});

test(`test failing ${coverage.flag}`, () => {
const result = spawnSync(process.execPath, [
'--test',
Expand Down
18 changes: 18 additions & 0 deletions test/parallel/test-runner-run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
for await (const _ of stream);
});

it('should emit diagnostic events with level parameter', async () => {
const diagnosticEvents = [];

const stream = run({
files: [join(testFixtures, 'coverage.js')],
reporter: 'spec',
});

stream.on('test:diagnostic', (event) => {
diagnosticEvents.push(event);
});
// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
assert(diagnosticEvents.length > 0, 'No diagnostic events were emitted');
const infoEvent = diagnosticEvents.find((e) => e.level === 'info');
assert(infoEvent, 'No diagnostic events with level "info" were emitted');
});

const argPrintingFile = join(testFixtures, 'print-arguments.js');
it('should allow custom arguments via execArgv', async () => {
const result = await run({ files: [argPrintingFile], execArgv: ['-p', '"Printed"'] }).compose(spec).toArray();
Expand Down
Loading