'use strict' const test = require('node:test') const assert = require('node:assert') const { once } = require('node:events') const { join } = require('node:path') const { MessageChannel } = require('node:worker_threads') const ThreadStream = require('thread-stream') const tspl = require('@matteo.collina/tspl') const match = require('./match') workerTest('transport-on-data.js') workerTest('transport-async-iteration.js', ' when using async iteration') function workerTest (filename, description = '') { test(`does not wait for pino to send config by default${description}`, async function (t) { const plan = tspl(t, { plan: 4 }) const { port1, port2 } = new MessageChannel() const stream = new ThreadStream({ filename: join(__dirname, 'fixtures', filename), workerData: { port: port1 }, workerOpts: { transferList: [port1] } }) const expected = [{ level: 30, time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'hello world' }, { level: 30, time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'another message', prop: 42 }] const emptyPinoConfig = { levels: undefined, messageKey: undefined, errorKey: undefined } port2.on('message', function (message) { match(expected.shift(), message.data, { assert: plan }) match(emptyPinoConfig, message.pinoConfig, { assert: plan }) }) const lines = expected.map(JSON.stringify).join('\n') stream.write(lines) stream.end() await plan }) test(`does not wait for pino to send config if transport is not expecting it${description}`, async function (t) { const plan = tspl(t, { plan: 4 }) const { port1, port2 } = new MessageChannel() const stream = new ThreadStream({ filename: join(__dirname, 'fixtures', filename), workerData: { port: port1, pinoWillSendConfig: true }, workerOpts: { transferList: [port1] } }) const expected = [{ level: 30, time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'hello world' }, { level: 30, time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'another message', prop: 42 }] const emptyPinoConfig = { levels: undefined, messageKey: undefined, errorKey: undefined } const pinoConfig = { levels: { labels: { 30: 'info' }, values: { info: 30 } }, messageKey: 'msg', errorKey: 'err' } stream.emit('message', { code: 'PINO_CONFIG', config: pinoConfig }) port2.on('message', function (message) { match(expected.shift(), message.data, { assert: plan }) match(emptyPinoConfig, message.pinoConfig, { assert: plan }) }) const lines = expected.map(JSON.stringify).join('\n') stream.write(lines) stream.end() await plan }) test(`waits for the pino config when pino intends to send it and the transport requests it${description}`, async function (t) { const plan = tspl(t, { plan: 4 }) const { port1, port2 } = new MessageChannel() const stream = new ThreadStream({ filename: join(__dirname, 'fixtures', filename), workerData: { port: port1, pinoWillSendConfig: true, opts: { expectPinoConfig: true } }, workerOpts: { transferList: [port1] } }) const expected = [{ level: 30, time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'hello world' }, { level: 30, time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'another message', prop: 42 }] const pinoConfig = { levels: { labels: { 30: 'info' }, values: { info: 30 } }, messageKey: 'msg', errorKey: 'err' } port2.on('message', function (message) { match(expected.shift(), message.data, { assert: plan }) match(pinoConfig, message.pinoConfig, { assert: plan }) }) const lines = expected.map(JSON.stringify).join('\n') stream.emit('message', { code: 'PINO_CONFIG', config: pinoConfig }) stream.write(lines) stream.end() await plan }) test(`continues to listen if it receives a message that is not PINO_CONFIG${description}`, async function (t) { const plan = tspl(t, { plan: 4 }) const { port1, port2 } = new MessageChannel() const stream = new ThreadStream({ filename: join(__dirname, 'fixtures', 'transport-on-data.js'), workerData: { port: port1, pinoWillSendConfig: true, opts: { expectPinoConfig: true } }, workerOpts: { transferList: [port1] } }) const expected = [{ level: 30, time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'hello world' }, { level: 30, time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'another message', prop: 42 }] const pinoConfig = { levels: { labels: { 30: 'info' }, values: { info: 30 } }, messageKey: 'msg', errorKey: 'err' } port2.on('message', function (message) { match(expected.shift(), message.data, { assert: plan }) match(pinoConfig, message.pinoConfig, { assert: plan }) }) const lines = expected.map(JSON.stringify).join('\n') stream.emit('message', 'not a PINO_CONFIG') stream.emit('message', { code: 'NOT_PINO_CONFIG', config: { levels: 'foo', messageKey: 'bar', errorKey: 'baz' } }) stream.emit('message', { code: 'PINO_CONFIG', config: pinoConfig }) stream.write(lines) stream.end() await plan }) test(`waits for the pino config even if it is sent after write${description}`, async function (t) { const plan = tspl(t, { plan: 4 }) const { port1, port2 } = new MessageChannel() const stream = new ThreadStream({ filename: join(__dirname, 'fixtures', filename), workerData: { port: port1, pinoWillSendConfig: true, opts: { expectPinoConfig: true } }, workerOpts: { transferList: [port1] } }) const expected = [{ level: 30, time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'hello world' }, { level: 30, time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'another message', prop: 42 }] const pinoConfig = { levels: { labels: { 30: 'info' }, values: { info: 30 } }, messageKey: 'msg', errorKey: 'err' } port2.on('message', function (message) { match(expected.shift(), message.data, { assert: plan }) match(pinoConfig, message.pinoConfig, { assert: plan }) }) const lines = expected.map(JSON.stringify).join('\n') stream.write(lines) stream.emit('message', { code: 'PINO_CONFIG', config: pinoConfig }) stream.end() await plan }) test(`emits an error if the transport expects pino to send the config, but pino is not going to${description}`, async function () { const stream = new ThreadStream({ filename: join(__dirname, 'fixtures', filename), workerData: { opts: { expectPinoConfig: true } } }) const [err] = await once(stream, 'error') assert.equal(err.message, 'This transport is not compatible with the current version of pino. Please upgrade pino to the latest version.') assert.ok(stream.destroyed) }) } test('waits for the pino config when pipelining', async function (t) { const plan = tspl(t, { plan: 2 }) const { port1, port2 } = new MessageChannel() const stream = new ThreadStream({ filename: join(__dirname, 'fixtures', 'worker-pipeline.js'), workerData: { pinoWillSendConfig: true, targets: [{ target: './transport-transform.js', options: { opts: { expectPinoConfig: true } } }, { target: './transport-on-data.js', options: { port: port1 } }] }, workerOpts: { transferList: [port1] } }) const expected = [{ level: 'info(30)', time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'HELLO WORLD', service: 'from transform' }, { level: 'info(30)', time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'ANOTHER MESSAGE', prop: 42, service: 'from transform' }] const lines = [{ level: 30, time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'hello world' }, { level: 30, time: 1617955768092, pid: 2942, hostname: 'MacBook-Pro.local', msg: 'another message', prop: 42 }].map(JSON.stringify).join('\n') const pinoConfig = { levels: { labels: { 30: 'info' }, values: { info: 30 } }, messageKey: 'msg', errorKey: 'err' } port2.on('message', function (message) { match(expected.shift(), message.data, { assert: plan }) }) stream.emit('message', { code: 'PINO_CONFIG', config: pinoConfig }) stream.write(lines) stream.end() await plan })