current commit
251019.2 · fix: prevent duplicate debug logs when multiple undici instances exist (#4630)
'use strict'

const { test } = require('node:test')
const { spawn } = require('node:child_process')
const { join } = require('node:path')
const { tspl } = require('@matteo.collina/tspl')

// eslint-disable-next-line no-control-regex
const removeEscapeColorsRE = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g

const isNode23Plus = process.versions.node.split('.')[0] >= 23
const isCITGM = !!process.env.CITGM

test('debug#websocket', { skip: !process.versions.icu || isCITGM || isNode23Plus }, async t => {
  const assert = tspl(t, { plan: 6 })
  const child = spawn(
    process.execPath,
    [
      '--no-experimental-fetch',
      join(__dirname, '../fixtures/websocket.js')],
    {
      env: {
        NODE_DEBUG: 'websocket'
      }
    }
  )
  const chunks = []
  const assertions = [
    /(WEBSOCKET [0-9]+:) (connecting to)/,
    /(WEBSOCKET [0-9]+:) (connected to)/,
    /(WEBSOCKET [0-9]+:) (sending request)/,
    /(WEBSOCKET [0-9]+:) (connection opened)/,
    /(WEBSOCKET [0-9]+:) (closed connection to)/,
    /^$/
  ]

  child.stderr.setEncoding('utf8')
  child.stderr.on('data', chunk => {
    chunks.push(chunk)
  })
  child.stderr.on('end', () => {
    const lines = extractLines(chunks)
    assert.strictEqual(lines.length, assertions.length)
    for (let i = 1; i < lines.length; i++) {
      assert.match(lines[i], assertions[i])
    }
  })

  await assert.completed
})

test('debug#fetch', { skip: isCITGM || isNode23Plus }, async t => {
  const assert = tspl(t, { plan: 7 })
  const child = spawn(
    process.execPath,
    [
      '--no-experimental-fetch',
      join(__dirname, '../fixtures/fetch.js')
    ],
    {
      env: Object.assign({}, process.env, { NODE_DEBUG: 'fetch' })
    }
  )
  const chunks = []
  const assertions = [
    /(FETCH [0-9]+:) (connecting to)/,
    /(FETCH [0-9]+:) (connected to)/,
    /(FETCH [0-9]+:) (sending request)/,
    /(FETCH [0-9]+:) (received response)/,
    /(FETCH [0-9]+:) (trailers received)/,
    /^$/
  ]

  child.stderr.setEncoding('utf8')
  child.stderr.on('data', chunk => {
    chunks.push(chunk)
  })
  child.stderr.on('end', () => {
    const lines = extractLines(chunks)
    assert.strictEqual(lines.length, assertions.length)
    for (let i = 0; i < lines.length; i++) {
      assert.match(lines[i], assertions[i])
    }
  })

  await assert.completed
})

test('debug#undici', { skip: isCITGM || isNode23Plus }, async t => {
  // Due to Node.js webpage redirect
  const assert = tspl(t, { plan: 7 })
  const child = spawn(
    process.execPath,
    [
      '--no-experimental-fetch',
      join(__dirname, '../fixtures/undici.js')
    ],
    {
      env: {
        NODE_DEBUG: 'undici'
      }
    }
  )
  const chunks = []
  const assertions = [
    /(UNDICI [0-9]+:) (connecting to)/,
    /(UNDICI [0-9]+:) (connected to)/,
    /(UNDICI [0-9]+:) (sending request)/,
    /(UNDICI [0-9]+:) (received response)/,
    /(UNDICI [0-9]+:) (trailers received)/,
    /^$/
  ]

  child.stderr.setEncoding('utf8')
  child.stderr.on('data', chunk => {
    chunks.push(chunk)
  })
  child.stderr.on('end', () => {
    const lines = extractLines(chunks)
    assert.strictEqual(lines.length, assertions.length)
    for (let i = 0; i < lines.length; i++) {
      assert.match(lines[i], assertions[i])
    }
  })

  await assert.completed
})

test('debug#undici no duplicates', { skip: isCITGM || isNode23Plus }, async t => {
  const assert = tspl(t, { plan: 7 })
  const child = spawn(
    process.execPath,
    [
      '--no-experimental-fetch',
      join(__dirname, '../fixtures/duplicate-debug.js')
    ],
    {
      env: {
        NODE_DEBUG: 'undici'
      }
    }
  )
  const chunks = []
  const assertions = [
    /(UNDICI [0-9]+:) (connecting to)/,
    /(UNDICI [0-9]+:) (connected to)/,
    /(UNDICI [0-9]+:) (sending request)/,
    /(UNDICI [0-9]+:) (received response)/,
    /(UNDICI [0-9]+:) (trailers received)/,
    /^$/
  ]

  child.stderr.setEncoding('utf8')
  child.stderr.on('data', chunk => {
    chunks.push(chunk)
  })
  child.stderr.on('end', () => {
    const lines = extractLines(chunks)
    // Should have exactly the expected number of lines, no duplicates
    assert.strictEqual(lines.length, assertions.length, 'Should not have duplicate log lines')
    for (let i = 0; i < lines.length; i++) {
      assert.match(lines[i], assertions[i])
    }
  })

  await assert.completed
})

function extractLines (chunks) {
  return chunks
    .join('')
    .split('\n')
    .map(v => v.replace(removeEscapeColorsRE, ''))
}