diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index bf46ca62881b71..29b4d597b799aa 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -177,6 +177,7 @@ function setup(root) { topLevel: 0, suites: 0, }, + shouldColorizeTestFiles: false, }; root.startTime = hrtime(); return root; diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index af060cc014528e..656d4b2c51234a 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -351,6 +351,9 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) { stdio.push('ipc'); env.WATCH_REPORT_DEPENDENCIES = '1'; } + if (root.harness.shouldColorizeTestFiles) { + env.FORCE_COLOR = '1'; + } const child = spawn(process.execPath, args, { signal: t.signal, encoding: 'utf8', env, stdio }); if (watchMode) { diff --git a/lib/internal/test_runner/utils.js b/lib/internal/test_runner/utils.js index a7674e8a086245..2029fb7ea68642 100644 --- a/lib/internal/test_runner/utils.js +++ b/lib/internal/test_runner/utils.js @@ -17,7 +17,7 @@ const { createWriteStream } = require('fs'); const { pathToFileURL } = require('internal/url'); const { createDeferredPromise } = require('internal/util'); const { getOptionValue } = require('internal/options'); -const { green, red, white } = require('internal/util/colors'); +const { green, red, white, shouldColorize } = require('internal/util/colors'); const { codes: { @@ -116,9 +116,10 @@ function tryBuiltinReporter(name) { return require(builtinPath); } -async function getReportersMap(reporters, destinations) { +async function getReportersMap(reporters, destinations, rootTest) { return SafePromiseAllReturnArrayLike(reporters, async (name, i) => { const destination = kBuiltinDestinations.get(destinations[i]) ?? createWriteStream(destinations[i]); + rootTest.harness.shouldColorizeTestFiles ||= shouldColorize(destination); // Load the test reporter passed to --test-reporter let reporter = tryBuiltinReporter(name); @@ -155,7 +156,7 @@ async function getReportersMap(reporters, destinations) { async function setupTestReporters(rootTest) { const { reporters, destinations } = parseCommandLine(); - const reportersMap = await getReportersMap(reporters, destinations); + const reportersMap = await getReportersMap(reporters, destinations, rootTest); for (let i = 0; i < reportersMap.length; i++) { const { reporter, destination } = reportersMap[i]; compose(rootTest.reporter, reporter).pipe(destination); diff --git a/test/fixtures/test-runner/output/arbitrary-output-colored-1.js b/test/fixtures/test-runner/output/arbitrary-output-colored-1.js new file mode 100644 index 00000000000000..0fd1018e632278 --- /dev/null +++ b/test/fixtures/test-runner/output/arbitrary-output-colored-1.js @@ -0,0 +1,7 @@ +'use strict'; + +const test = require('node:test'); +console.log({ foo: 'bar' }); +test('passing test', () => { + console.log(1); +}); diff --git a/test/fixtures/test-runner/output/arbitrary-output-colored.js b/test/fixtures/test-runner/output/arbitrary-output-colored.js new file mode 100644 index 00000000000000..b09eeeb9971cf6 --- /dev/null +++ b/test/fixtures/test-runner/output/arbitrary-output-colored.js @@ -0,0 +1,11 @@ +'use strict'; +const common = require('../../../common'); +const { once } = require('node:events'); +const { spawn } = require('node:child_process'); +const fixtures = require('../../../common/fixtures'); + +(async function run() { + const test = fixtures.path('test-runner/output/arbitrary-output-colored-1.js'); + await once(spawn(process.execPath, ['--test', test], { stdio: 'inherit', env: { FORCE_COLOR: 1 } }), 'exit'); + await once(spawn(process.execPath, ['--test', '--test-reporter', 'tap', test], { stdio: 'inherit', env: { FORCE_COLOR: 1 } }), 'exit'); +})().then(common.mustCall()); diff --git a/test/fixtures/test-runner/output/arbitrary-output-colored.snapshot b/test/fixtures/test-runner/output/arbitrary-output-colored.snapshot new file mode 100644 index 00000000000000..87866602f04559 --- /dev/null +++ b/test/fixtures/test-runner/output/arbitrary-output-colored.snapshot @@ -0,0 +1,32 @@ +{ foo: [32m'bar'[39m } + +[33m1[39m + +[32m✔ passing test [90m(*ms)[39m[39m +[34mℹ tests 1[39m +[34mℹ suites 0[39m +[34mℹ pass 1[39m +[34mℹ fail 0[39m +[34mℹ cancelled 0[39m +[34mℹ skipped 0[39m +[34mℹ todo 0[39m +[34mℹ duration_ms *[39m +TAP version 13 +# { foo: [32m'bar'[39m } +# +# [33m1[39m +# +# Subtest: passing test +ok 1 - passing test + --- + duration_ms: * + ... +1..1 +# tests 1 +# suites 0 +# pass 1 +# fail 0 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms * diff --git a/test/parallel/test-runner-output.mjs b/test/parallel/test-runner-output.mjs index bd8c9f1bd071ee..0d670c37bc9319 100644 --- a/test/parallel/test-runner-output.mjs +++ b/test/parallel/test-runner-output.mjs @@ -3,6 +3,10 @@ import * as fixtures from '../common/fixtures.mjs'; import * as snapshot from '../common/assertSnapshot.js'; import { describe, it } from 'node:test'; +const skipForceColors = + process.config.variables.icu_gyp_path !== 'tools/icu/icu-generic.gyp' || + process.config.variables.node_shared_openssl; + function replaceTestDuration(str) { return str .replaceAll(/duration_ms: 0(\r?\n)/g, 'duration_ms: ZERO$1') @@ -46,8 +50,14 @@ const tests = [ { name: 'test-runner/output/unresolved_promise.js' }, { name: 'test-runner/output/default_output.js', transform: specTransform, tty: true }, { name: 'test-runner/output/arbitrary-output.js' }, + !skipForceColors ? { + name: 'test-runner/output/arbitrary-output-colored.js', + transform: snapshot.transform(specTransform, replaceTestDuration), tty: true + } : false, { name: 'test-runner/output/dot_output_custom_columns.js', transform: specTransform, tty: true }, -].map(({ name, tty, transform }) => ({ +] +.filter(Boolean) +.map(({ name, tty, transform }) => ({ name, fn: common.mustCall(async () => { await snapshot.spawnAndAssert(fixtures.path(name), transform ?? defaultTransform, { tty });