From d35d49ac87c07af4dcf0e4b681083ea378815c70 Mon Sep 17 00:00:00 2001 From: Moshe Atlow Date: Mon, 19 Dec 2022 19:35:57 +0200 Subject: [PATCH] feat: add reporters PR-URL: https://github.com/nodejs/node/pull/45712 Fixes: https://github.com/nodejs/node/issues/45648 Reviewed-By: Antoine du Hamel Reviewed-By: Colin Ihrig Reviewed-By: Benjamin Gruenbaum (cherry picked from commit a1b27b25bb01aadd3fd2714e4b136db11b7eb85a) --- README.md | 240 ++++++- bin/node-core-test.js | 14 +- lib/internal/main/test_runner.js | 9 +- lib/internal/per_context/primordials.js | 3 + lib/internal/test_runner/harness.js | 6 +- lib/internal/test_runner/reporter/dot.js | 18 + lib/internal/test_runner/reporter/spec.js | 110 +++ .../{tap_stream.js => reporter/tap.js} | 149 ++-- lib/internal/test_runner/runner.js | 35 +- lib/internal/test_runner/test.js | 54 +- lib/internal/test_runner/tests_stream.js | 75 ++ lib/internal/test_runner/utils.js | 77 ++- .../{yaml_parser.js => yaml_to_js.js} | 2 +- lib/internal/util/colors.js | 26 + .../test-runner/custom_reporters/custom.cjs | 18 + .../test-runner/custom_reporters/custom.js | 9 + .../test-runner/custom_reporters/custom.mjs | 9 + test/fixtures/test-runner/reporters.js | 12 + test/message.js | 4 +- test/message/test_runner_desctibe_it.out | 7 - test/message/test_runner_hooks.out | 3 - test/message/test_runner_output.js | 16 +- test/message/test_runner_output.out | 7 - test/message/test_runner_output_cli.js | 8 + test/message/test_runner_output_cli.out | 647 ++++++++++++++++++ .../test_runner_output_dot_reporter.js | 7 + .../test_runner_output_dot_reporter.out | 4 + .../test_runner_output_spec_reporter.js | 11 + .../test_runner_output_spec_reporter.out | 260 +++++++ test/parallel/test-runner-cli.js | 2 - test/parallel/test-runner-exit-code.js | 5 +- test/parallel/test-runner-reporters.js | 100 +++ test/parallel/test-runner-run.mjs | 3 +- 33 files changed, 1729 insertions(+), 221 deletions(-) create mode 100644 lib/internal/test_runner/reporter/dot.js create mode 100644 lib/internal/test_runner/reporter/spec.js rename lib/internal/test_runner/{tap_stream.js => reporter/tap.js} (61%) create mode 100644 lib/internal/test_runner/tests_stream.js rename lib/internal/test_runner/{yaml_parser.js => yaml_to_js.js} (96%) create mode 100644 lib/internal/util/colors.js create mode 100644 test/fixtures/test-runner/custom_reporters/custom.cjs create mode 100644 test/fixtures/test-runner/custom_reporters/custom.js create mode 100644 test/fixtures/test-runner/custom_reporters/custom.mjs create mode 100644 test/fixtures/test-runner/reporters.js create mode 100644 test/message/test_runner_output_cli.js create mode 100644 test/message/test_runner_output_cli.out create mode 100644 test/message/test_runner_output_dot_reporter.js create mode 100644 test/message/test_runner_output_dot_reporter.out create mode 100644 test/message/test_runner_output_spec_reporter.js create mode 100644 test/message/test_runner_output_spec_reporter.out create mode 100644 test/parallel/test-runner-reporters.js diff --git a/README.md b/README.md index 2ff9fa3..d422753 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,7 @@ Differences from the core implementation: -The `node:test` module facilitates the creation of JavaScript tests that -report results in [TAP][] format. This package is a port of `node:test`. +The `node:test` module facilitates the creation of JavaScript tests. To access it: ```mjs @@ -99,9 +98,7 @@ test('callback failing test', (t, done) => { }) ``` -As a test file executes, TAP is written to the standard output of the Node.js -process. This output can be interpreted by any test harness that understands -the TAP format. If any tests fail, the process exit code is set to `1`. +If any tests fail, the process exit code is set to `1`. #### Subtests @@ -130,8 +127,7 @@ test to fail. ## Skipping tests Individual tests can be skipped by passing the `skip` option to the test, or by -calling the test context's `skip()` method. Both of these options support -including a message that is displayed in the TAP output as shown in the +calling the test context's `skip()` method as shown in the following example. ```js @@ -265,7 +261,7 @@ Test name patterns do not change the set of files that the test runner executes. ## Extraneous asynchronous activity -Once a test function finishes executing, the TAP results are output as quickly +Once a test function finishes executing, the results are reported as quickly as possible while maintaining the order of the tests. However, it is possible for the test function to generate asynchronous activity that outlives the test itself. The test runner handles this type of activity, but does not delay the @@ -274,13 +270,13 @@ reporting of test results in order to accommodate it. In the following example, a test completes with two `setImmediate()` operations still outstanding. The first `setImmediate()` attempts to create a new subtest. Because the parent test has already finished and output its -results, the new subtest is immediately marked as failed, and reported in the -top level of the file's TAP output. +results, the new subtest is immediately marked as failed, and reported later +to the {TestsStream}. The second `setImmediate()` creates an `uncaughtException` event. `uncaughtException` and `unhandledRejection` events originating from a completed test are marked as failed by the `test` module and reported as diagnostic -warnings in the top level of the file's TAP output. +warnings at the top level by the {TestsStream}. ```js test('a test that creates asynchronous activity', t => { @@ -431,6 +427,163 @@ test('spies on an object method', (t) => { }); ``` + +## Test reporters + + + +The `node:test` module supports passing [`--test-reporter`][] +flags for the test runner to use a specific reporter. + +The following built-reporters are supported: + +* `tap` + The `tap` reporter is the default reporter used by the test runner. It outputs + the test results in the [TAP][] format. + +* `spec` + The `spec` reporter outputs the test results in a human-readable format. + +* `dot` + The `dot` reporter outputs the test results in a comact format, + where each passing test is represented by a `.`, + and each failing test is represented by a `X`. + +### Custom reporters + +[`--test-reporter`][] can be used to specify a path to custom reporter. +a custom reporter is a module that exports a value +accepted by [stream.compose][]. +Reporters should transform events emitted by a {TestsStream} + +Example of a custom reporter using {stream.Transform}: + +```mjs +import { Transform } from 'node:stream'; +const customReporter = new Transform({ + writableObjectMode: true, + transform(event, encoding, callback) { + switch (event.type) { + case 'test:start': + callback(null, `test ${event.data.name} started`); + break; + case 'test:pass': + callback(null, `test ${event.data.name} passed`); + break; + case 'test:fail': + callback(null, `test ${event.data.name} failed`); + break; + case 'test:plan': + callback(null, 'test plan'); + break; + case 'test:diagnostic': + callback(null, event.data.message); + break; + } + }, +}); +export default customReporter; +``` + +```cjs +const { Transform } = require('node:stream'); +const customReporter = new Transform({ + writableObjectMode: true, + transform(event, encoding, callback) { + switch (event.type) { + case 'test:start': + callback(null, `test ${event.data.name} started`); + break; + case 'test:pass': + callback(null, `test ${event.data.name} passed`); + break; + case 'test:fail': + callback(null, `test ${event.data.name} failed`); + break; + case 'test:plan': + callback(null, 'test plan'); + break; + case 'test:diagnostic': + callback(null, event.data.message); + break; + } + }, +}); +module.exports = customReporter; +``` + +Example of a custom reporter using a generator function: + +```mjs +export default async function * customReporter(source) { + for await (const event of source) { + switch (event.type) { + case 'test:start': + yield `test ${event.data.name} started\n`; + break; + case 'test:pass': + yield `test ${event.data.name} passed\n`; + break; + case 'test:fail': + yield `test ${event.data.name} failed\n`; + break; + case 'test:plan': + yield 'test plan'; + break; + case 'test:diagnostic': + yield `${event.data.message}\n`; + break; + } + } +} +``` + +```cjs +module.exports = async function * customReporter(source) { + for await (const event of source) { + switch (event.type) { + case 'test:start': + yield `test ${event.data.name} started\n`; + break; + case 'test:pass': + yield `test ${event.data.name} passed\n`; + break; + case 'test:fail': + yield `test ${event.data.name} failed\n`; + break; + case 'test:plan': + yield 'test plan\n'; + break; + case 'test:diagnostic': + yield `${event.data.message}\n`; + break; + } + } +}; +``` + +### Multiple reporters + +The [`--test-reporter`][] flag can be specified multiple times to report test +results in several formats. In this situation +it is required to specify a destination for each reporter +using [`--test-reporter-destination`][]. +Destination can be `stdout`, `stderr`, or a file path. +Reporters and destinations are paired according +to the order they were specified. + +In the following example, the `spec` reporter will output to `stdout`, +and the `dot` reporter will output to `file.txt`: + +```bash +node --test-reporter=spec --test-reporter=dot --test-reporter-destination=stdout --test-reporter-destination=file.txt +``` + +When a single reporter is specified, the destination will default to `stdout`, +unless a destination is explicitly provided. + ## `run([options])`