diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index deabbc6dc78..28f66097a3e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -5,26 +5,40 @@ on: schedule: - cron: "0 10 * * *" +permissions: + contents: read + jobs: - test: + test-linux: + if: github.repository == 'nodejs/undici' + uses: ./.github/workflows/test.yml + with: + node-version: 22-nightly + runs-on: ubuntu-latest + secrets: inherit + + test-windows: + if: github.repository == 'nodejs/undici' + uses: ./.github/workflows/test.yml + with: + node-version: 22-nightly + runs-on: windows-latest + secrets: inherit + + test-macos: if: github.repository == 'nodejs/undici' - strategy: - fail-fast: false - max-parallel: 0 - matrix: - runs-on: - - ubuntu-latest - - windows-latest - - macos-latest uses: ./.github/workflows/test.yml with: node-version: 22-nightly - runs-on: ${{ matrix.runs-on }} + runs-on: macos-latest secrets: inherit report-failure: - if: failure() - needs: test + if: ${{ always() && (needs.test-linux.result == 'failure' && needs.test-windows.result == 'failure' && needs.test-macos.result == 'failure') }} + needs: + - test-linux + - test-windows + - test-macos runs-on: ubuntu-latest permissions: issues: write diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 6373d4ac7e7..882134e8b93 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -69,6 +69,77 @@ jobs: runs-on: ${{ matrix.runs-on }} secrets: inherit + test-without-intl: + name: Test with Node.js ${{ matrix.version }} compiled --without-intl + strategy: + fail-fast: false + max-parallel: 0 + matrix: + version: [20, 21] + runs-on: ubuntu-latest + timeout-minutes: 120 + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false + + # Setup node, install deps, and build undici prior to building icu-less node and testing + - name: Setup Node.js@${{ inputs.version }} + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: ${{ inputs.version }} + + - name: Install dependencies + run: npm install + + - name: Build undici + run: npm run build:node + + - name: Determine latest release + id: release + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + result-encoding: string + script: | + const req = await fetch('https://nodejs.org/download/release/index.json') + const releases = await req.json() + + const latest = releases.find((r) => r.version.startsWith('v${{ matrix.version }}')) + return latest.version + + - name: Download and extract source + run: curl https://nodejs.org/download/release/${{ steps.release.outputs.result }}/node-${{ steps.release.outputs.result }}.tar.xz | tar xfJ - + + - name: Install ninja + run: sudo apt-get install ninja-build + + - name: ccache + uses: hendrikmuhs/ccache-action@faf867a11c028c0b483fb2ae72b6fc8f7d842714 #v1.2.12 + with: + key: node${{ matrix.version }} + + - name: Build node + working-directory: ./node-${{ steps.release.outputs.result }} + run: | + export CC="ccache gcc" + export CXX="ccache g++" + ./configure --without-intl --ninja --prefix=./final + make + make install + echo "$(pwd)/final/bin" >> $GITHUB_PATH + + - name: Print version information + run: | + echo OS: $(node -p "os.version()") + echo Node.js: $(node --version) + echo npm: $(npm --version) + echo git: $(git --version) + echo icu config: $(node -e "console.log(process.config)" | grep icu) + + - name: Run tests + run: npm run test:javascript:withoutintl + test-types: name: Test TypeScript types timeout-minutes: 15 @@ -97,12 +168,12 @@ jobs: - dependency-review - test - test-types + - test-without-intl - lint runs-on: ubuntu-latest permissions: contents: write pull-requests: write - actions: write steps: - name: Merge Dependabot PR uses: fastify/github-action-merge-dependabot@9e7bfb249c69139d7bdcd8d984f9665edd49020b # v3.10.1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a23d5edec71..9dd62828a39 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,6 +10,9 @@ on: required: true type: string +permissions: + contents: read + jobs: test: name: Test with Node.js ${{ inputs.node-version }} on ${{ inputs.runs-on }} diff --git a/README.md b/README.md index 72c32de1346..476c0280c8f 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,12 @@ An HTTP/1.1 client, written from scratch for Node.js. > Undici means eleven in Italian. 1.1 -> 11 -> Eleven -> Undici. It is also a Stranger Things reference. +## How to get involved + Have a question about using Undici? Open a [Q&A Discussion](https://github.com/nodejs/undici/discussions/new) or join our official OpenJS [Slack](https://openjs-foundation.slack.com/archives/C01QF9Q31QD) channel. +Looking to contribute? Start by reading the [contributing guide](./CONTRIBUTING.md) + ## Install ``` diff --git a/lib/dispatcher/client-h2.js b/lib/dispatcher/client-h2.js index 0a53b75e501..6b48ab9904e 100644 --- a/lib/dispatcher/client-h2.js +++ b/lib/dispatcher/client-h2.js @@ -405,6 +405,18 @@ function writeH2 (client, request) { const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers request.onResponseStarted() + // Due to the stream nature, it is possible we face a race condition + // where the stream has been assigned, but the request has been aborted + // the request remains in-flight and headers hasn't been received yet + // for those scenarios, best effort is to destroy the stream immediately + // as there's no value to keep it open. + if (request.aborted || request.completed) { + const err = new RequestAbortedError() + errorRequest(client, request, err) + util.destroy(stream, err) + return + } + if (request.onHeaders(Number(statusCode), parseH2Headers(realHeaders), stream.resume.bind(stream), '') === false) { stream.pause() } diff --git a/lib/mock/pending-interceptors-formatter.js b/lib/mock/pending-interceptors-formatter.js index ba6e4ebce1b..ccca951195a 100644 --- a/lib/mock/pending-interceptors-formatter.js +++ b/lib/mock/pending-interceptors-formatter.js @@ -3,6 +3,9 @@ const { Transform } = require('node:stream') const { Console } = require('node:console') +const PERSISTENT = process.versions.icu ? '✅' : 'Y ' +const NOT_PERSISTENT = process.versions.icu ? '❌' : 'N ' + /** * Gets the output of `console.table(…)` as a string. */ @@ -29,7 +32,7 @@ module.exports = class PendingInterceptorsFormatter { Origin: origin, Path: path, 'Status code': statusCode, - Persistent: persist ? '✅' : '❌', + Persistent: persist ? PERSISTENT : NOT_PERSISTENT, Invocations: timesInvoked, Remaining: persist ? Infinity : times - timesInvoked })) diff --git a/lib/web/fetch/headers.js b/lib/web/fetch/headers.js index b849b0e356c..2235f125c65 100644 --- a/lib/web/fetch/headers.js +++ b/lib/web/fetch/headers.js @@ -12,7 +12,7 @@ const { } = require('./util') const { webidl } = require('./webidl') const assert = require('node:assert') -const util = require('util') +const util = require('node:util') const kHeadersMap = Symbol('headers map') const kHeadersSortedMap = Symbol('headers map sorted') diff --git a/package.json b/package.json index a3dc801c4dc..10ecfa3befe 100644 --- a/package.json +++ b/package.json @@ -69,10 +69,13 @@ "lint:fix": "standard --fix | snazzy", "test": "npm run test:javascript && cross-env NODE_V8_COVERAGE= npm run test:typescript", "test:javascript": "node scripts/generate-pem && npm run test:unit && npm run test:node-fetch && npm run test:fetch && npm run test:cookies && npm run test:eventsource && npm run test:wpt && npm run test:websocket && npm run test:node-test && npm run test:jest", + "test:javascript:withoutintl": "node scripts/generate-pem && npm run test:unit && npm run test:node-fetch && npm run test:fetch:nobuild && npm run test:cookies && npm run test:eventsource:nobuild && npm run test:wpt:withoutintl && npm run test:node-test", "test:cookies": "borp -p \"test/cookie/*.js\"", "test:node-fetch": "borp -p \"test/node-fetch/**/*.js\"", - "test:eventsource": "npm run build:node && borp --expose-gc -p \"test/eventsource/*.js\"", - "test:fetch": "npm run build:node && borp --expose-gc -p \"test/fetch/*.js\" && borp -p \"test/webidl/*.js\" && borp -p \"test/busboy/*.js\"", + "test:eventsource": "npm run build:node && npm run test:eventsource:nobuild", + "test:eventsource:nobuild": "borp --expose-gc -p \"test/eventsource/*.js\"", + "test:fetch": "npm run build:node && npm run test:fetch:nobuild", + "test:fetch:nobuild": "borp --expose-gc -p \"test/fetch/*.js\" && borp -p \"test/webidl/*.js\" && borp -p \"test/busboy/*.js\"", "test:jest": "cross-env NODE_V8_COVERAGE= jest", "test:unit": "borp --expose-gc -p \"test/*.js\"", "test:node-test": "borp -p \"test/node-test/**/*.js\"", @@ -81,6 +84,7 @@ "test:typescript": "tsd && tsc --skipLibCheck test/imports/undici-import.ts", "test:websocket": "borp -p \"test/websocket/*.js\"", "test:wpt": "node test/wpt/start-fetch.mjs && node test/wpt/start-FileAPI.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-websockets.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs", + "test:wpt:withoutintl": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs", "coverage": "npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report", "coverage:ci": "npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report:ci", "coverage:clean": "node ./scripts/clean-coverage.js", diff --git a/test/connect-timeout.js b/test/connect-timeout.js index 98f7c466bbf..0a28e1f250d 100644 --- a/test/connect-timeout.js +++ b/test/connect-timeout.js @@ -9,7 +9,7 @@ const assert = require('node:assert') // Using describe instead of test to avoid the timeout describe('prioritize socket errors over timeouts', async () => { const t = tspl({ ...assert, after: () => {} }, { plan: 1 }) - const client = new Pool('http://foobar.bar:1234', { connectTimeout: 1 }) + const client = new Pool('http://foorbar.invalid:1234', { connectTimeout: 1 }) client.request({ method: 'GET', path: '/foobar' }) .then(() => t.fail()) diff --git a/test/fetch/headers-inspect-custom.js b/test/fetch/headers-inspect-custom.js index 1aa3326e98c..8145e9d7536 100644 --- a/test/fetch/headers-inspect-custom.js +++ b/test/fetch/headers-inspect-custom.js @@ -3,7 +3,7 @@ const { Headers } = require('../../lib/web/fetch/headers') const { test } = require('node:test') const assert = require('node:assert') -const util = require('util') +const util = require('node:util') test('Headers class custom inspection', () => { const headers = new Headers() diff --git a/test/fetch/request-inspect-custom.js b/test/fetch/request-inspect-custom.js index 7cb7fc73152..e2e60bdab7b 100644 --- a/test/fetch/request-inspect-custom.js +++ b/test/fetch/request-inspect-custom.js @@ -1,8 +1,8 @@ 'use strict' const { describe, it } = require('node:test') -const assert = require('assert') -const util = require('util') +const assert = require('node:assert') +const util = require('node:util') const { Request } = require('../../') describe('Request custom inspection', () => { diff --git a/test/fetch/response-inspect-custom.js b/test/fetch/response-inspect-custom.js index bf72a053a6e..ca8a5a0fc1a 100644 --- a/test/fetch/response-inspect-custom.js +++ b/test/fetch/response-inspect-custom.js @@ -1,8 +1,8 @@ 'use strict' const { describe, it } = require('node:test') -const assert = require('assert') -const util = require('util') +const assert = require('node:assert') +const util = require('node:util') const { Response } = require('../../') describe('Response custom inspection', () => { diff --git a/test/http2.js b/test/http2.js index 849a0cc43ba..a4ecc1ac3e8 100644 --- a/test/http2.js +++ b/test/http2.js @@ -1296,3 +1296,88 @@ test('Should throw informational error on half-closed streams (remote)', async t t.strictEqual(err.code, 'UND_ERR_INFO') }) }) + +test('#2364 - Concurrent aborts', async t => { + const server = createSecureServer(pem) + + server.on('stream', (stream, headers, _flags, rawHeaders) => { + t.strictEqual(headers['x-my-header'], 'foo') + t.strictEqual(headers[':method'], 'GET') + setTimeout(() => { + stream.respond({ + 'content-type': 'text/plain; charset=utf-8', + 'x-custom-h2': 'hello', + ':status': 200 + }) + stream.end('hello h2!') + }, 100) + }) + + server.listen(0) + await once(server, 'listening') + + const client = new Client(`https://localhost:${server.address().port}`, { + connect: { + rejectUnauthorized: false + }, + allowH2: true + }) + + t = tspl(t, { plan: 18 }) + after(() => server.close()) + after(() => client.close()) + const controller = new AbortController() + + client.request({ + path: '/', + method: 'GET', + headers: { + 'x-my-header': 'foo' + } + }, (err, response) => { + t.ifError(err) + t.strictEqual(response.headers['content-type'], 'text/plain; charset=utf-8') + t.strictEqual(response.headers['x-custom-h2'], 'hello') + t.strictEqual(response.statusCode, 200) + response.body.dump() + }) + + client.request({ + path: '/', + method: 'GET', + headers: { + 'x-my-header': 'foo' + }, + signal: controller.signal + }, (err, response) => { + t.strictEqual(err.name, 'AbortError') + }) + + client.request({ + path: '/', + method: 'GET', + headers: { + 'x-my-header': 'foo' + } + }, (err, response) => { + t.ifError(err) + t.strictEqual(response.headers['content-type'], 'text/plain; charset=utf-8') + t.strictEqual(response.headers['x-custom-h2'], 'hello') + t.strictEqual(response.statusCode, 200) + }) + + client.request({ + path: '/', + method: 'GET', + headers: { + 'x-my-header': 'foo' + }, + signal: controller.signal + }, (err, response) => { + t.strictEqual(err.name, 'AbortError') + }) + + controller.abort() + + await t.completed +}) diff --git a/test/mock-interceptor-unused-assertions.js b/test/mock-interceptor-unused-assertions.js index e6f5360a3f9..5be3942d3db 100644 --- a/test/mock-interceptor-unused-assertions.js +++ b/test/mock-interceptor-unused-assertions.js @@ -10,6 +10,10 @@ const util = require('../lib/core/util') // https://github.com/nodejs/node/pull/50135 const tableRowsAlignedToLeft = util.nodeMajor >= 21 || (util.nodeMajor === 20 && util.nodeMinor >= 11) +// `console.table` treats emoji as two character widths for cell width determination +const Y = process.versions.icu ? '✅' : 'Y ' +const N = process.versions.icu ? '❌' : 'N ' + // Avoid colors in the output for inline snapshots. const pendingInterceptorsFormatter = new PendingInterceptorsFormatter({ disableColors: true }) @@ -55,7 +59,7 @@ test('1 pending interceptor', t => { ┌─────────┬────────┬───────────────────────┬──────┬─────────────┬────────────┬─────────────┬───────────┐ │ (index) │ Method │ Origin │ Path │ Status code │ Persistent │ Invocations │ Remaining │ ├─────────┼────────┼───────────────────────┼──────┼─────────────┼────────────┼─────────────┼───────────┤ -│ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '❌' │ 0 │ 1 │ +│ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '${N}' │ 0 │ 1 │ └─────────┴────────┴───────────────────────┴──────┴─────────────┴────────────┴─────────────┴───────────┘ `.trim() : ` @@ -64,7 +68,7 @@ test('1 pending interceptor', t => { ┌─────────┬────────┬───────────────────────┬──────┬─────────────┬────────────┬─────────────┬───────────┐ │ (index) │ Method │ Origin │ Path │ Status code │ Persistent │ Invocations │ Remaining │ ├─────────┼────────┼───────────────────────┼──────┼─────────────┼────────────┼─────────────┼───────────┤ -│ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '❌' │ 0 │ 1 │ +│ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '${N}' │ 0 │ 1 │ └─────────┴────────┴───────────────────────┴──────┴─────────────┴────────────┴─────────────┴───────────┘ `.trim()) } @@ -88,8 +92,8 @@ test('2 pending interceptors', t => { ┌─────────┬────────┬──────────────────────────┬──────────────┬─────────────┬────────────┬─────────────┬───────────┐ │ (index) │ Method │ Origin │ Path │ Status code │ Persistent │ Invocations │ Remaining │ ├─────────┼────────┼──────────────────────────┼──────────────┼─────────────┼────────────┼─────────────┼───────────┤ -│ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '❌' │ 0 │ 1 │ -│ 1 │ 'GET' │ 'https://localhost:9999' │ '/some/path' │ 204 │ '❌' │ 0 │ 1 │ +│ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '${N}' │ 0 │ 1 │ +│ 1 │ 'GET' │ 'https://localhost:9999' │ '/some/path' │ 204 │ '${N}' │ 0 │ 1 │ └─────────┴────────┴──────────────────────────┴──────────────┴─────────────┴────────────┴─────────────┴───────────┘ `.trim() : ` @@ -98,8 +102,8 @@ test('2 pending interceptors', t => { ┌─────────┬────────┬──────────────────────────┬──────────────┬─────────────┬────────────┬─────────────┬───────────┐ │ (index) │ Method │ Origin │ Path │ Status code │ Persistent │ Invocations │ Remaining │ ├─────────┼────────┼──────────────────────────┼──────────────┼─────────────┼────────────┼─────────────┼───────────┤ -│ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '❌' │ 0 │ 1 │ -│ 1 │ 'GET' │ 'https://localhost:9999' │ '/some/path' │ 204 │ '❌' │ 0 │ 1 │ +│ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '${N}' │ 0 │ 1 │ +│ 1 │ 'GET' │ 'https://localhost:9999' │ '/some/path' │ 204 │ '${N}' │ 0 │ 1 │ └─────────┴────────┴──────────────────────────┴──────────────┴─────────────┴────────────┴─────────────┴───────────┘ `.trim()) } @@ -164,10 +168,10 @@ test('Variations of persist(), times(), and pending status', async t => { ┌─────────┬────────┬──────────────────────────┬──────────────────────┬─────────────┬────────────┬─────────────┬───────────┐ │ (index) │ Method │ Origin │ Path │ Status code │ Persistent │ Invocations │ Remaining │ ├─────────┼────────┼──────────────────────────┼──────────────────────┼─────────────┼────────────┼─────────────┼───────────┤ -│ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '❌' │ 0 │ 1 │ -│ 1 │ 'GET' │ 'https://localhost:9999' │ '/persistent/unused' │ 200 │ '✅' │ 0 │ Infinity │ -│ 2 │ 'GET' │ 'https://localhost:9999' │ '/times/partial' │ 200 │ '❌' │ 1 │ 4 │ -│ 3 │ 'GET' │ 'https://localhost:9999' │ '/times/unused' │ 200 │ '❌' │ 0 │ 2 │ +│ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '${N}' │ 0 │ 1 │ +│ 1 │ 'GET' │ 'https://localhost:9999' │ '/persistent/unused' │ 200 │ '${Y}' │ 0 │ Infinity │ +│ 2 │ 'GET' │ 'https://localhost:9999' │ '/times/partial' │ 200 │ '${N}' │ 1 │ 4 │ +│ 3 │ 'GET' │ 'https://localhost:9999' │ '/times/unused' │ 200 │ '${N}' │ 0 │ 2 │ └─────────┴────────┴──────────────────────────┴──────────────────────┴─────────────┴────────────┴─────────────┴───────────┘ `.trim() : ` @@ -176,10 +180,10 @@ test('Variations of persist(), times(), and pending status', async t => { ┌─────────┬────────┬──────────────────────────┬──────────────────────┬─────────────┬────────────┬─────────────┬───────────┐ │ (index) │ Method │ Origin │ Path │ Status code │ Persistent │ Invocations │ Remaining │ ├─────────┼────────┼──────────────────────────┼──────────────────────┼─────────────┼────────────┼─────────────┼───────────┤ -│ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '❌' │ 0 │ 1 │ -│ 1 │ 'GET' │ 'https://localhost:9999' │ '/persistent/unused' │ 200 │ '✅' │ 0 │ Infinity │ -│ 2 │ 'GET' │ 'https://localhost:9999' │ '/times/partial' │ 200 │ '❌' │ 1 │ 4 │ -│ 3 │ 'GET' │ 'https://localhost:9999' │ '/times/unused' │ 200 │ '❌' │ 0 │ 2 │ +│ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '${N}' │ 0 │ 1 │ +│ 1 │ 'GET' │ 'https://localhost:9999' │ '/persistent/unused' │ 200 │ '${Y}' │ 0 │ Infinity │ +│ 2 │ 'GET' │ 'https://localhost:9999' │ '/times/partial' │ 200 │ '${N}' │ 1 │ 4 │ +│ 3 │ 'GET' │ 'https://localhost:9999' │ '/times/unused' │ 200 │ '${N}' │ 0 │ 2 │ └─────────┴────────┴──────────────────────────┴──────────────────────┴─────────────┴────────────┴─────────────┴───────────┘ `.trim()) } @@ -229,7 +233,7 @@ test('defaults to rendering output with terminal color when process.env.CI is un ┌─────────┬────────┬───────────────────────┬──────┬─────────────┬────────────┬─────────────┬───────────┐ │ (index) │ Method │ Origin │ Path │ Status code │ Persistent │ Invocations │ Remaining │ ├─────────┼────────┼───────────────────────┼──────┼─────────────┼────────────┼─────────────┼───────────┤ -│ 0 │ \u001b[32m'GET'\u001b[39m │ \u001b[32m'https://example.com'\u001b[39m │ \u001b[32m'/'\u001b[39m │ \u001b[33m200\u001b[39m │ \u001b[32m'❌'\u001b[39m │ \u001b[33m0\u001b[39m │ \u001b[33m1\u001b[39m │ +│ 0 │ \u001b[32m'GET'\u001b[39m │ \u001b[32m'https://example.com'\u001b[39m │ \u001b[32m'/'\u001b[39m │ \u001b[33m200\u001b[39m │ \u001b[32m'${N}'\u001b[39m │ \u001b[33m0\u001b[39m │ \u001b[33m1\u001b[39m │ └─────────┴────────┴───────────────────────┴──────┴─────────────┴────────────┴─────────────┴───────────┘ `.trim() : ` @@ -238,7 +242,7 @@ test('defaults to rendering output with terminal color when process.env.CI is un ┌─────────┬────────┬───────────────────────┬──────┬─────────────┬────────────┬─────────────┬───────────┐ │ (index) │ Method │ Origin │ Path │ Status code │ Persistent │ Invocations │ Remaining │ ├─────────┼────────┼───────────────────────┼──────┼─────────────┼────────────┼─────────────┼───────────┤ -│ 0 │ \u001b[32m'GET'\u001b[39m │ \u001b[32m'https://example.com'\u001b[39m │ \u001b[32m'/'\u001b[39m │ \u001b[33m200\u001b[39m │ \u001b[32m'❌'\u001b[39m │ \u001b[33m0\u001b[39m │ \u001b[33m1\u001b[39m │ +│ 0 │ \u001b[32m'GET'\u001b[39m │ \u001b[32m'https://example.com'\u001b[39m │ \u001b[32m'/'\u001b[39m │ \u001b[33m200\u001b[39m │ \u001b[32m'${N}'\u001b[39m │ \u001b[33m0\u001b[39m │ \u001b[33m1\u001b[39m │ └─────────┴────────┴───────────────────────┴──────┴─────────────┴────────────┴─────────────┴───────────┘ `.trim()) diff --git a/test/node-test/debug.js b/test/node-test/debug.js index 3e6ca0bc0ef..d7c462f57ae 100644 --- a/test/node-test/debug.js +++ b/test/node-test/debug.js @@ -8,7 +8,7 @@ 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 -test('debug#websocket', async t => { +test('debug#websocket', { skip: !process.versions.icu }, async t => { const assert = tspl(t, { plan: 8 }) const child = spawn( process.execPath,