From e6e6bd60d0b3afd7813524bc59225fb2f6e2e11f Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 2 Mar 2026 17:00:41 +0100 Subject: [PATCH 1/2] test: migrate tap suite to node:test --- examples/plugin-with-preloaded.js | 7 +- package.json | 6 +- test/graceful-shutdown.test.js | 36 ++++----- test/start.test.js | 118 +++++++++++++++++++++++------- 4 files changed, 118 insertions(+), 49 deletions(-) diff --git a/examples/plugin-with-preloaded.js b/examples/plugin-with-preloaded.js index 5dc93f5a..51d59154 100644 --- a/examples/plugin-with-preloaded.js +++ b/examples/plugin-with-preloaded.js @@ -1,13 +1,14 @@ /* global GLOBAL_MODULE_1, GLOBAL_MODULE_3 */ 'use strict' -const t = require('tap') + +const assert = require('node:assert/strict') module.exports = async function (fastify, options) { fastify.get('/', async function (req, reply) { return { hasPreloaded: GLOBAL_MODULE_1 && GLOBAL_MODULE_3 } }) fastify.addHook('onReady', function () { - t.ok(GLOBAL_MODULE_1) - t.ok(GLOBAL_MODULE_3) + assert.ok(GLOBAL_MODULE_1) + assert.ok(GLOBAL_MODULE_3) }) } diff --git a/package.json b/package.json index 315e5a2c..c74a56b9 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "unit:suites": "node should-skip-test-suites.js || npm run all-suites", "all-suites": "npm run unit:cjs && npm run unit:esm && npm run unit:ts-cjs && npm run unit:ts-esm", "unit:cli-js-esm": "node suite-runner.js \"test/esm/**/*.test.js\"", - "unit:cli-js": "tap \"test/**/*.test.js\" --no-coverage --timeout 400 --jobs 1 --color -R specy", - "unit:cli-ts": "cross-env TS_NODE_PROJECT=./test/configs/ts-cjs.tsconfig.json tap \"test/**/*.test.ts\" --no-coverage --timeout 400 --jobs 1 --color -R specy", + "unit:cli-js": "node suite-runner.js \"test/**/*.test.js\"", + "unit:cli-ts": "cross-env TS_NODE_PROJECT=./test/configs/ts-cjs.tsconfig.json node -r ts-node/register suite-runner.js \"test/**/*.test.ts\"", "unit:cli": "npm run unit:cli-js && npm run unit:cli-ts && npm run unit:cli-js-esm", "test:cli-and-typescript": "npm run unit:cli && npm run test:typescript", "test:typescript": "tsd templates/plugin -t ./../../index.d.ts && tsc --project templates/app-ts/tsconfig.json --noEmit && tsc --project templates/app-ts-esm/tsconfig.json --noEmit" @@ -66,7 +66,6 @@ "@fastify/autoload": "^6.0.0", "@fastify/sensible": "^6.0.0", "@types/node": "^25.0.3", - "@types/tap": "^15.0.5", "c8": "^11.0.0", "concurrently": "^9.0.0", "cross-env": "^10.0.0", @@ -79,7 +78,6 @@ "rimraf": "^6.1.0", "sinon": "^21.0.0", "strip-ansi": "^6.0.1", - "tap": "^16.1.0", "ts-node": "^10.4.0", "ts-standard": "^12.0.1", "tsd": "^0.33.0", diff --git a/test/graceful-shutdown.test.js b/test/graceful-shutdown.test.js index 4b9196e0..589321cf 100644 --- a/test/graceful-shutdown.test.js +++ b/test/graceful-shutdown.test.js @@ -1,8 +1,10 @@ 'use strict' -const t = require('tap') +const { once } = require('node:events') +const { test, beforeEach, afterEach } = require('node:test') +const assert = require('node:assert/strict') // Tests skip on win32 platforms due SIGINT signal is not supported across all windows platforms -const test = (process.platform === 'win32') ? t.skip : t.test +const testFn = (process.platform === 'win32') ? (name, fn) => test(name, { skip: true }, fn) : test const sinon = require('sinon') const start = require('../start') @@ -17,7 +19,7 @@ let fastify = null let signalCounter = null const sandbox = sinon.createSandbox() -t.beforeEach(async () => { +beforeEach(async () => { signalCounter = process.listenerCount('SIGINT') const argv = ['-p', getPort(), './examples/plugin.js'] @@ -25,30 +27,30 @@ t.beforeEach(async () => { spy = sinon.spy(fastify, 'close') }) -t.afterEach(async () => { +afterEach(async () => { sandbox.restore() }) -test('should add and remove SIGINT listener as expected ', async t => { - t.plan(2) - - t.equal(process.listenerCount('SIGINT'), signalCounter + 1) +testFn('should add and remove SIGINT listener as expected ', async () => { + assert.equal(process.listenerCount('SIGINT'), signalCounter + 1) await fastify.close() - t.equal(process.listenerCount('SIGINT'), signalCounter) - - t.end() + assert.equal(process.listenerCount('SIGINT'), signalCounter) }) -test('should have called fastify.close() when receives a SIGINT signal', async t => { - process.once('SIGINT', () => { - sinon.assert.called(spy) - - t.end() +testFn('should have called fastify.close() when receives a SIGINT signal', async (t) => { + const exit = process.exit + process.exit = sinon.spy() - process.exit() + t.after(() => { + process.exit = exit }) + const sigintPromise = once(process, 'SIGINT') + process.kill(process.pid, 'SIGINT') + await sigintPromise + + sinon.assert.called(spy) }) diff --git a/test/start.test.js b/test/start.test.js index ca5f4986..35300171 100644 --- a/test/start.test.js +++ b/test/start.test.js @@ -7,12 +7,13 @@ const fs = require('node:fs') const path = require('node:path') const crypto = require('node:crypto') const semver = require('semver') +const os = require('node:os') const baseFilename = path.join(__dirname, 'fixtures', `test_${crypto.randomBytes(16).toString('hex')}`) const { fork } = require('node:child_process') +const { test: nodeTest } = require('node:test') +const assert = require('node:assert/strict') const moduleSupport = semver.satisfies(process.version, '>= 14 || >= 12.17.0 < 13.0.0') -const t = require('tap') -const test = t.test const sinon = require('sinon') const proxyquire = require('proxyquire').noPreserveCache() const start = require('../start') @@ -20,6 +21,62 @@ const start = require('../start') const writeFile = util.promisify(fs.writeFile) const readFile = util.promisify(fs.readFile) +function createTestDir (fixtures) { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'fastify-cli-')) + + function writeFixtures (baseDir, entries) { + for (const [name, value] of Object.entries(entries)) { + const filename = path.join(baseDir, name) + if (value && typeof value === 'object' && !Buffer.isBuffer(value)) { + fs.mkdirSync(filename, { recursive: true }) + writeFixtures(filename, value) + } else { + fs.mkdirSync(path.dirname(filename), { recursive: true }) + fs.writeFileSync(filename, value) + } + } + } + + writeFixtures(dir, fixtures) + return dir +} + +function tapCompatTest (name, options, fn) { + if (typeof options === 'function') { + fn = options + options = undefined + } + + return nodeTest(name, options, async (t) => { + let planned = null + let assertions = 0 + const count = (method) => (...args) => { + assertions++ + return method(...args) + } + + t.plan = (total) => { + planned = total + } + t.equal = count(assert.strictEqual) + t.same = count(assert.deepStrictEqual) + t.ok = count(assert.ok) + t.pass = count(() => assert.ok(true)) + t.fail = count((message) => assert.fail(message)) + t.end = () => {} + t.teardown = (hook) => t.after(async () => hook()) + t.testdir = createTestDir + + await fn(t) + + if (planned !== null) { + assert.strictEqual(assertions, planned, `plan expected ${planned} assertions but received ${assertions}`) + } + }) +} + +const test = tapCompatTest + function requireUncached (module) { delete require.cache[require.resolve(module)] return require(module) @@ -307,30 +364,38 @@ test('should warn on file not found', t => { start.start(argv) }) -test('should throw on package not found', t => { +test('should throw on package not found', async t => { t.plan(1) const oldStop = start.stop t.teardown(() => { start.stop = oldStop }) - start.stop = function (err) { - t.ok(/Cannot find module 'unknown-package'/.test(err.message), err.message) - } - const argv = ['-p', getPort(), './test/data/package-not-found.js'] - start.start(argv) + await new Promise((resolve) => { + start.stop = function (err) { + t.ok(/Cannot find module 'unknown-package'/.test(err.message), err.message) + resolve() + } + + const argv = ['-p', getPort(), './test/data/package-not-found.js'] + start.start(argv) + }) }) -test('should throw on parsing error', t => { +test('should throw on parsing error', async t => { t.plan(1) const oldStop = start.stop t.teardown(() => { start.stop = oldStop }) - start.stop = function (err) { - t.equal(err.constructor, SyntaxError) - } - const argv = ['-p', getPort(), './test/data/parsing-error.js'] - start.start(argv) + await new Promise((resolve) => { + start.stop = function (err) { + t.equal(err.constructor, SyntaxError) + resolve() + } + + const argv = ['-p', getPort(), './test/data/parsing-error.js'] + start.start(argv) + }) }) test('should start the server with an async/await plugin', async t => { @@ -372,17 +437,21 @@ test('should exit without error on help', t => { t.end() }) -test('should throw the right error on require file', t => { +test('should throw the right error on require file', async t => { t.plan(1) const oldStop = start.stop t.teardown(() => { start.stop = oldStop }) - start.stop = function (err) { - t.ok(/undefinedVariable is not defined/.test(err.message), err.message) - } - const argv = ['-p', getPort(), './test/data/undefinedVariable.js'] - start.start(argv) + await new Promise((resolve) => { + start.stop = function (err) { + t.ok(/undefinedVariable is not defined/.test(err.message), err.message) + resolve() + } + + const argv = ['-p', getPort(), './test/data/undefinedVariable.js'] + start.start(argv) + }) }) test('should respond 413 - Payload too large', async t => { @@ -712,14 +781,13 @@ test('should read env variables from .env file', async (t) => { await fastify.close() }) -test('crash on unhandled rejection', t => { +test('crash on unhandled rejection', async t => { t.plan(1) const argv = ['-p', getPort(), './test/data/rejection.js'] const child = fork(path.join(__dirname, '..', 'start.js'), argv, { silent: true }) - child.on('close', function (code) { - t.equal(code, 1) - }) + const [code] = await once(child, 'close') + t.equal(code, 1) }) test('should start the server with inspect options and the defalut port is 9320', async t => { @@ -925,7 +993,7 @@ test('preloading a built-in module works', async t => { test('preloading a module in node_modules works', async t => { t.plan(1) - const argv = ['-r', 'tap', './examples/plugin.js'] + const argv = ['-r', 'semver', './examples/plugin.js'] const fastify = await start.start(argv) await fastify.close() t.pass('server closed') From 5e9cb0ff3161c18bf170a6768ce16231a2d18f7f Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 5 Mar 2026 10:52:34 +0100 Subject: [PATCH 2/2] test: align node:test alias usage in graceful shutdown tests --- test/graceful-shutdown.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/graceful-shutdown.test.js b/test/graceful-shutdown.test.js index 589321cf..5c3257e9 100644 --- a/test/graceful-shutdown.test.js +++ b/test/graceful-shutdown.test.js @@ -1,10 +1,10 @@ 'use strict' const { once } = require('node:events') -const { test, beforeEach, afterEach } = require('node:test') +const { test: nodeTest, beforeEach, afterEach } = require('node:test') const assert = require('node:assert/strict') // Tests skip on win32 platforms due SIGINT signal is not supported across all windows platforms -const testFn = (process.platform === 'win32') ? (name, fn) => test(name, { skip: true }, fn) : test +const test = (process.platform === 'win32') ? (name, fn) => nodeTest(name, { skip: true }, fn) : nodeTest const sinon = require('sinon') const start = require('../start') @@ -31,7 +31,7 @@ afterEach(async () => { sandbox.restore() }) -testFn('should add and remove SIGINT listener as expected ', async () => { +test('should add and remove SIGINT listener as expected ', async () => { assert.equal(process.listenerCount('SIGINT'), signalCounter + 1) await fastify.close() @@ -39,7 +39,7 @@ testFn('should add and remove SIGINT listener as expected ', async () => { assert.equal(process.listenerCount('SIGINT'), signalCounter) }) -testFn('should have called fastify.close() when receives a SIGINT signal', async (t) => { +test('should have called fastify.close() when receives a SIGINT signal', async (t) => { const exit = process.exit process.exit = sinon.spy()