node/test/parallel/test-runner-reporters.js
Colin Ihrig 0c90be9072
test_runner: better handle async bootstrap errors
The test runner is bootstrapped synchronously, with the exception
of importing custom reporters. To better handle asynchronously
throw errors, this commit introduces an internal error type that
can be checked for from the test runner's uncaughtException
handler.

After https://github.com/nodejs/node/pull/46707 and this change
land, the other throw statement in the uncaughtException handler
can be removed. This will allow the test runner to handle errors
thrown from outside of tests (which currently prevents the test
runner from reporting results).

PR-URL: https://github.com/nodejs/node/pull/46720
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
2023-02-24 14:13:24 +00:00

132 lines
6.2 KiB
JavaScript

'use strict';
require('../common');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
const { describe, it } = require('node:test');
const { spawnSync } = require('node:child_process');
const assert = require('node:assert');
const path = require('node:path');
const fs = require('node:fs');
const testFile = fixtures.path('test-runner/reporters.js');
tmpdir.refresh();
let tmpFiles = 0;
describe('node:test reporters', { concurrency: true }, () => {
it('should default to outputing TAP to stdout', async () => {
const child = spawnSync(process.execPath, ['--test', testFile]);
assert.strictEqual(child.stderr.toString(), '');
assert.match(child.stdout.toString(), /TAP version 13/);
assert.match(child.stdout.toString(), /ok 1 - ok/);
assert.match(child.stdout.toString(), /not ok 2 - failing/);
assert.match(child.stdout.toString(), /ok 2 - top level/);
});
it('should default destination to stdout when passing a single reporter', async () => {
const child = spawnSync(process.execPath, ['--test', '--test-reporter', 'dot', testFile]);
assert.strictEqual(child.stderr.toString(), '');
assert.strictEqual(child.stdout.toString(), '.XX.\n');
});
it('should throw when passing reporters without a destination', async () => {
const child = spawnSync(process.execPath, ['--test', '--test-reporter', 'dot', '--test-reporter', 'tap', testFile]);
assert.match(child.stderr.toString(), /The argument '--test-reporter' must match the number of specified '--test-reporter-destination'\. Received \[ 'dot', 'tap' \]/);
assert.strictEqual(child.stdout.toString(), '');
});
it('should throw when passing a destination without a reporter', async () => {
const child = spawnSync(process.execPath, ['--test', '--test-reporter-destination', 'tap', testFile]);
assert.match(child.stderr.toString(), /The argument '--test-reporter' must match the number of specified '--test-reporter-destination'\. Received \[\]/);
assert.strictEqual(child.stdout.toString(), '');
});
it('should support stdout as a destination', async () => {
const child = spawnSync(process.execPath,
['--test', '--test-reporter', 'dot', '--test-reporter-destination', 'stdout', testFile]);
assert.strictEqual(child.stderr.toString(), '');
assert.strictEqual(child.stdout.toString(), '.XX.\n');
});
it('should support stderr as a destination', async () => {
const child = spawnSync(process.execPath,
['--test', '--test-reporter', 'dot', '--test-reporter-destination', 'stderr', testFile]);
assert.strictEqual(child.stderr.toString(), '.XX.\n');
assert.strictEqual(child.stdout.toString(), '');
});
it('should support a file as a destination', async () => {
const file = path.join(tmpdir.path, `${tmpFiles++}.out`);
const child = spawnSync(process.execPath,
['--test', '--test-reporter', 'dot', '--test-reporter-destination', file, testFile]);
assert.strictEqual(child.stderr.toString(), '');
assert.strictEqual(child.stdout.toString(), '');
assert.strictEqual(fs.readFileSync(file, 'utf8'), '.XX.\n');
});
it('should support multiple reporters', async () => {
const file = path.join(tmpdir.path, `${tmpFiles++}.out`);
const file2 = path.join(tmpdir.path, `${tmpFiles++}.out`);
const child = spawnSync(process.execPath,
['--test',
'--test-reporter', 'dot', '--test-reporter-destination', file,
'--test-reporter', 'spec', '--test-reporter-destination', file2,
'--test-reporter', 'tap', '--test-reporter-destination', 'stdout',
testFile]);
assert.match(child.stdout.toString(), /TAP version 13/);
assert.match(child.stdout.toString(), /# duration_ms/);
assert.strictEqual(fs.readFileSync(file, 'utf8'), '.XX.\n');
const file2Contents = fs.readFileSync(file2, 'utf8');
assert.match(file2Contents, /▶ nested/);
assert.match(file2Contents, /✔ ok/);
assert.match(file2Contents, /✖ failing/);
});
['js', 'cjs', 'mjs'].forEach((ext) => {
it(`should support a '${ext}' file as a custom reporter`, async () => {
const filename = `custom.${ext}`;
const child = spawnSync(process.execPath,
['--test', '--test-reporter', fixtures.fileURL('test-runner/custom_reporters/', filename),
testFile]);
assert.strictEqual(child.stderr.toString(), '');
const stdout = child.stdout.toString();
assert.match(stdout, /{"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/);
assert.strictEqual(stdout.slice(0, filename.length + 2), `${filename} {`);
});
});
it('should support a custom reporter from node_modules', async () => {
const child = spawnSync(process.execPath,
['--test', '--test-reporter', 'reporter-cjs', 'reporters.js'],
{ cwd: fixtures.path('test-runner') });
assert.strictEqual(child.stderr.toString(), '');
assert.match(
child.stdout.toString(),
/^package: reporter-cjs{"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/,
);
});
it('should support a custom ESM reporter from node_modules', async () => {
const child = spawnSync(process.execPath,
['--test', '--test-reporter', 'reporter-esm', 'reporters.js'],
{ cwd: fixtures.path('test-runner') });
assert.strictEqual(child.stderr.toString(), '');
assert.match(
child.stdout.toString(),
/^package: reporter-esm{"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/,
);
});
it('should throw when reporter setup throws asynchronously', async () => {
const child = spawnSync(
process.execPath,
['--test', '--test-reporter', fixtures.fileURL('empty.js'), 'reporters.js'],
{ cwd: fixtures.path('test-runner') }
);
assert.strictEqual(child.status, 7);
assert.strictEqual(child.signal, null);
assert.strictEqual(child.stdout.toString(), '');
assert.match(child.stderr.toString(), /ERR_INVALID_ARG_TYPE/);
});
});