From 36b861868b07b2fb74d686e7b7c7cbbf429152f8 Mon Sep 17 00:00:00 2001 From: Bob Evans Date: Thu, 21 Nov 2024 15:48:02 -0500 Subject: [PATCH] test: Migrated `test/integration/core` tests to `node:test` --- .borp.int.yaml | 1 + THIRD_PARTY_NOTICES.md | 53 +- package.json | 3 +- ...d_process.tap.js => child-process.test.js} | 180 +-- test/integration/core/crypto.tap.js | 135 --- test/integration/core/crypto.test.js | 124 ++ test/integration/core/dns.tap.js | 156 --- test/integration/core/dns.test.js | 155 +++ .../{exceptions.tap.js => exceptions.test.js} | 94 +- test/integration/core/fs.tap.js | 871 -------------- test/integration/core/fs.test.js | 1006 +++++++++++++++++ .../{inspector.tap.js => inspector.test.js} | 26 +- ...romises.tap.js => native-promises.test.js} | 548 ++++----- test/integration/core/net.tap.js | 212 ---- test/integration/core/net.test.js | 213 ++++ test/integration/core/promise-utils.js | 85 ++ test/integration/core/timers.tap.js | 298 ----- test/integration/core/timers.test.js | 312 +++++ test/integration/core/util.tap.js | 104 -- test/integration/core/util.test.js | 55 + test/integration/core/verify.js | 31 +- test/integration/core/zlib.tap.js | 171 --- test/integration/core/zlib.test.js | 182 +++ third_party_manifest.json | 111 +- 24 files changed, 2609 insertions(+), 2517 deletions(-) rename test/integration/core/{child_process.tap.js => child-process.test.js} (61%) delete mode 100644 test/integration/core/crypto.tap.js create mode 100644 test/integration/core/crypto.test.js delete mode 100644 test/integration/core/dns.tap.js create mode 100644 test/integration/core/dns.test.js rename test/integration/core/{exceptions.tap.js => exceptions.test.js} (56%) delete mode 100644 test/integration/core/fs.tap.js create mode 100644 test/integration/core/fs.test.js rename test/integration/core/{inspector.tap.js => inspector.test.js} (69%) rename test/integration/core/{native-promises/native-promises.tap.js => native-promises.test.js} (60%) delete mode 100644 test/integration/core/net.tap.js create mode 100644 test/integration/core/net.test.js create mode 100644 test/integration/core/promise-utils.js delete mode 100644 test/integration/core/timers.tap.js create mode 100644 test/integration/core/timers.test.js delete mode 100644 test/integration/core/util.tap.js create mode 100644 test/integration/core/util.test.js delete mode 100644 test/integration/core/zlib.tap.js create mode 100644 test/integration/core/zlib.test.js diff --git a/.borp.int.yaml b/.borp.int.yaml index 74626df765..221f6a0a1a 100644 --- a/.borp.int.yaml +++ b/.borp.int.yaml @@ -5,3 +5,4 @@ files: - 'test/integration/*.test.js' - 'test/integration/cat/*.test.js' - 'test/integration/config/*.test.js' + - 'test/integration/core/**/*.test.js' diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index c89397551d..69e3ecf220 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -75,7 +75,6 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic * [sinon](#sinon) * [superagent](#superagent) * [tap](#tap) -* [temp](#temp) **[optionalDependencies](#optionalDependencies)** @@ -1042,7 +1041,7 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ### winston-transport -This product includes source derived from [winston-transport](https://github.com/winstonjs/winston-transport) ([v4.8.0](https://github.com/winstonjs/winston-transport/tree/v4.8.0)), distributed under the [MIT License](https://github.com/winstonjs/winston-transport/blob/v4.8.0/LICENSE): +This product includes source derived from [winston-transport](https://github.com/winstonjs/winston-transport) ([v4.7.1](https://github.com/winstonjs/winston-transport/tree/v4.7.1)), distributed under the [MIT License](https://github.com/winstonjs/winston-transport/blob/v4.7.1/LICENSE): ``` The MIT License (MIT) @@ -1075,7 +1074,7 @@ SOFTWARE. ### @aws-sdk/client-s3 -This product includes source derived from [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3) ([v3.676.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.676.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.676.0/LICENSE): +This product includes source derived from [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3) ([v3.621.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.621.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.621.0/LICENSE): ``` Apache License @@ -1284,7 +1283,7 @@ This product includes source derived from [@aws-sdk/client-s3](https://github.co ### @aws-sdk/s3-request-presigner -This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.676.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.676.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.676.0/LICENSE): +This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.621.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.621.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.621.0/LICENSE): ``` Apache License @@ -1493,7 +1492,7 @@ This product includes source derived from [@aws-sdk/s3-request-presigner](https: ### @koa/router -This product includes source derived from [@koa/router](https://github.com/koajs/router) ([v12.0.2](https://github.com/koajs/router/tree/v12.0.2)), distributed under the [MIT License](https://github.com/koajs/router/blob/v12.0.2/LICENSE): +This product includes source derived from [@koa/router](https://github.com/koajs/router) ([v12.0.1](https://github.com/koajs/router/tree/v12.0.1)), distributed under the [MIT License](https://github.com/koajs/router/blob/v12.0.1/LICENSE): ``` The MIT License (MIT) @@ -2207,7 +2206,7 @@ THE SOFTWARE. ### @slack/bolt -This product includes source derived from [@slack/bolt](https://github.com/slackapi/bolt) ([v3.22.0](https://github.com/slackapi/bolt/tree/v3.22.0)), distributed under the [MIT License](https://github.com/slackapi/bolt/blob/v3.22.0/LICENSE): +This product includes source derived from [@slack/bolt](https://github.com/slackapi/bolt) ([v3.19.0](https://github.com/slackapi/bolt/tree/v3.19.0)), distributed under the [MIT License](https://github.com/slackapi/bolt/blob/v3.19.0/LICENSE): ``` The MIT License (MIT) @@ -2684,7 +2683,7 @@ SOFTWARE. ### async -This product includes source derived from [async](https://github.com/caolan/async) ([v3.2.6](https://github.com/caolan/async/tree/v3.2.6)), distributed under the [MIT License](https://github.com/caolan/async/blob/v3.2.6/LICENSE): +This product includes source derived from [async](https://github.com/caolan/async) ([v3.2.5](https://github.com/caolan/async/tree/v3.2.5)), distributed under the [MIT License](https://github.com/caolan/async/blob/v3.2.5/LICENSE): ``` Copyright (c) 2010-2018 Caolan McMahon @@ -2711,7 +2710,7 @@ THE SOFTWARE. ### aws-sdk -This product includes source derived from [aws-sdk](https://github.com/aws/aws-sdk-js) ([v2.1691.0](https://github.com/aws/aws-sdk-js/tree/v2.1691.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js/blob/v2.1691.0/LICENSE.txt): +This product includes source derived from [aws-sdk](https://github.com/aws/aws-sdk-js) ([v2.1665.0](https://github.com/aws/aws-sdk-js/tree/v2.1665.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js/blob/v2.1665.0/LICENSE.txt): ``` @@ -3139,7 +3138,7 @@ THE SOFTWARE. ### eslint-plugin-jsdoc -This product includes source derived from [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) ([v48.11.0](https://github.com/gajus/eslint-plugin-jsdoc/tree/v48.11.0)), distributed under the [BSD-3-Clause License](https://github.com/gajus/eslint-plugin-jsdoc/blob/v48.11.0/LICENSE): +This product includes source derived from [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) ([v48.10.2](https://github.com/gajus/eslint-plugin-jsdoc/tree/v48.10.2)), distributed under the [BSD-3-Clause License](https://github.com/gajus/eslint-plugin-jsdoc/blob/v48.10.2/LICENSE): ``` Copyright (c) 2018, Gajus Kuizinas (http://gajus.com/) @@ -3344,7 +3343,7 @@ Library. ### eslint -This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v8.57.1](https://github.com/eslint/eslint/tree/v8.57.1)), distributed under the [MIT License](https://github.com/eslint/eslint/blob/v8.57.1/LICENSE): +This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v8.57.0](https://github.com/eslint/eslint/tree/v8.57.0)), distributed under the [MIT License](https://github.com/eslint/eslint/blob/v8.57.0/LICENSE): ``` Copyright OpenJS Foundation and other contributors, @@ -3371,7 +3370,7 @@ THE SOFTWARE. ### express -This product includes source derived from [express](https://github.com/expressjs/express) ([v4.21.1](https://github.com/expressjs/express/tree/v4.21.1)), distributed under the [MIT License](https://github.com/expressjs/express/blob/v4.21.1/LICENSE): +This product includes source derived from [express](https://github.com/expressjs/express) ([v4.19.2](https://github.com/expressjs/express/tree/v4.19.2)), distributed under the [MIT License](https://github.com/expressjs/express/blob/v4.19.2/LICENSE): ``` (The MIT License) @@ -3507,7 +3506,7 @@ SOFTWARE. ### jsdoc -This product includes source derived from [jsdoc](https://github.com/jsdoc/jsdoc) ([v4.0.4](https://github.com/jsdoc/jsdoc/tree/v4.0.4)), distributed under the [Apache-2.0 License](https://github.com/jsdoc/jsdoc/blob/v4.0.4/LICENSE.md): +This product includes source derived from [jsdoc](https://github.com/jsdoc/jsdoc) ([v4.0.3](https://github.com/jsdoc/jsdoc/tree/v4.0.3)), distributed under the [Apache-2.0 License](https://github.com/jsdoc/jsdoc/blob/v4.0.3/LICENSE.md): ``` # License @@ -4002,7 +4001,7 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ### self-cert -This product includes source derived from [self-cert](https://github.com/jsumners/self-cert) ([v2.0.1](https://github.com/jsumners/self-cert/tree/v2.0.1)), distributed under the [MIT License](https://github.com/jsumners/self-cert/blob/v2.0.1/Readme.md): +This product includes source derived from [self-cert](https://github.com/jsumners/self-cert) ([v2.0.0](https://github.com/jsumners/self-cert/tree/v2.0.0)), distributed under the [MIT License](https://github.com/jsumners/self-cert/blob/v2.0.0/Readme.md): ``` MIT License @@ -4132,34 +4131,6 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ``` -### temp - -This product includes source derived from [temp](https://github.com/bruce/node-temp) ([v0.8.4](https://github.com/bruce/node-temp/tree/v0.8.4)), distributed under the [MIT License](https://github.com/bruce/node-temp/blob/v0.8.4/LICENSE): - -``` -The MIT License (MIT) - -Copyright (c) 2010-2014 Bruce Williams - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -``` - ## optionalDependencies diff --git a/package.json b/package.json index 157a13e730..7863465029 100644 --- a/package.json +++ b/package.json @@ -258,8 +258,7 @@ "should": "*", "sinon": "^5.1.1", "superagent": "^9.0.1", - "tap": "^16.3.4", - "temp": "^0.8.1" + "tap": "^16.3.4" }, "repository": { "type": "git", diff --git a/test/integration/core/child_process.tap.js b/test/integration/core/child-process.test.js similarity index 61% rename from test/integration/core/child_process.tap.js rename to test/integration/core/child-process.test.js index a8340903e7..f09fdc1999 100644 --- a/test/integration/core/child_process.tap.js +++ b/test/integration/core/child-process.test.js @@ -5,56 +5,71 @@ 'use strict' -const test = require('tap').test +const test = require('node:test') +const assert = require('node:assert') const cp = require('child_process') const fs = require('fs') const helper = require('../../lib/agent_helper') const verifySegments = require('./verify.js') const symbols = require('../../../lib/symbols') -test('exec', function (t) { - const agent = setupAgent(t) +test.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.instrumentMockedAgent() +}) + +test.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) +}) + +test('exec', function (t, end) { + const { agent } = t.nr helper.runInTransaction(agent, function () { cp.exec('ls', { cwd: __dirname }, function (err, stdout, stderr) { - t.notOk(err, 'should not error') + assert.ok(!err, 'should not error') const files = stdout.trim().split('\n').sort() - t.same(files, fs.readdirSync(__dirname).sort()) - t.equal(stderr, '') - verifySegments(t, agent, 'child_process.exec', ['child_process.execFile']) + assert.deepEqual(files, fs.readdirSync(__dirname).sort()) + assert.equal(stderr, '') + verifySegments({ + agent, + end, + name: 'child_process.exec', + children: ['child_process.execFile'] + }) }) }) }) -test('execFile', function (t) { - const agent = setupAgent(t) +test('execFile', function (t, end) { + const { agent } = t.nr helper.runInTransaction(agent, function () { cp.execFile('./exec-me.js', { cwd: __dirname }, function (err, stdout, stderr) { - t.notOk(err, 'should not error') - t.equal(stdout, 'I am stdout\n') - t.equal(stderr, 'I am stderr\n') - verifySegments(t, agent, 'child_process.execFile') + assert.ok(!err, 'should not error') + assert.equal(stdout, 'I am stdout\n') + assert.equal(stderr, 'I am stderr\n') + verifySegments({ agent, end, name: 'child_process.execFile' }) }) }) }) -test('transaction context is preserved in subscribed events', function (t) { - const agent = setupAgent(t) +test('transaction context is preserved in subscribed events', function (t, end) { + const { agent } = t.nr helper.runInTransaction(agent, function (transaction) { const child = cp.fork('./exec-me.js', { cwd: __dirname }) child.on('message', function () { - t.equal(agent.tracer.getTransaction(), transaction) + assert.equal(agent.tracer.getTransaction(), transaction) }) child.on('exit', function () { - t.equal(agent.tracer.getTransaction(), transaction) - t.end() + assert.equal(agent.tracer.getTransaction(), transaction) + end() }) }) }) -test('should not break removeListener for single event', (t) => { - const agent = setupAgent(t) +test('should not break removeListener for single event', (t, end) => { + const { agent } = t.nr helper.runInTransaction(agent, function () { const child = cp.fork('./exec-me.js', { cwd: __dirname }) @@ -62,19 +77,19 @@ test('should not break removeListener for single event', (t) => { function onMessage() {} child.on('message', onMessage) - t.ok(child._events.message) + assert.ok(child._events.message) child.removeListener('message', onMessage) - t.notOk(child._events.message) + assert.ok(!child._events.message) child.on('exit', function () { - t.end() + end() }) }) }) -test('should not break removeListener for multiple events down to single', (t) => { - const agent = setupAgent(t) +test('should not break removeListener for multiple events down to single', (t, end) => { + const { agent } = t.nr helper.runInTransaction(agent, function () { const child = cp.fork('./exec-me.js', { cwd: __dirname }) @@ -84,20 +99,20 @@ test('should not break removeListener for multiple events down to single', (t) = child.on('message', onMessage) child.on('message', onMessage2) - t.ok(child._events.message) + assert.ok(child._events.message) child.removeListener('message', onMessage) - t.ok(child._events.message) - t.equal(child._events.message[symbols.original], onMessage2) + assert.ok(child._events.message) + assert.equal(child._events.message[symbols.original], onMessage2) child.on('exit', function () { - t.end() + end() }) }) }) -test('should not break removeListener for multiple events down to multiple', (t) => { - const agent = setupAgent(t) +test('should not break removeListener for multiple events down to multiple', (t, end) => { + const { agent } = t.nr helper.runInTransaction(agent, function () { const child = cp.fork('./exec-me.js', { cwd: __dirname }) @@ -109,20 +124,20 @@ test('should not break removeListener for multiple events down to multiple', (t) child.on('message', onMessage) child.on('message', onMessage2) child.on('message', onMessage3) - t.ok(child._events.message) + assert.ok(child._events.message) child.removeListener('message', onMessage) - t.ok(child._events.message) - t.equal(child._events.message.length, 2) + assert.ok(child._events.message) + assert.equal(child._events.message.length, 2) child.on('exit', function () { - t.end() + end() }) }) }) -test('should not break once() removal of listener', (t) => { - const agent = setupAgent(t) +test('should not break once() removal of listener', (t, end) => { + const { agent } = t.nr helper.runInTransaction(agent, function () { const child = cp.fork('./exec-me.js', { cwd: __dirname }) @@ -130,18 +145,18 @@ test('should not break once() removal of listener', (t) => { let invokedMessage = false child.once('message', function onMessage() { invokedMessage = true - t.notOk(child._events.message) + assert.ok(!child._events.message) }) child.on('exit', function () { - t.ok(invokedMessage, 'Must have onMessage called for test to be valid.') - t.end() + assert.ok(invokedMessage, 'Must have onMessage called for test to be valid.') + end() }) }) }) -test('should not break multiple once() for multiple events down to single', (t) => { - const agent = setupAgent(t) +test('should not break multiple once() for multiple events down to single', (t, end) => { + const { agent } = t.nr helper.runInTransaction(agent, function () { const child = cp.fork('./exec-me.js', { cwd: __dirname }) @@ -160,17 +175,17 @@ test('should not break multiple once() for multiple events down to single', (t) child.on('message', onMessage3) child.on('exit', function () { - t.ok(invokedMessage1, 'Must have onMessage called for test to be valid.') - t.ok(invokedMessage2, 'Must have onMessage2 called for test to be valid.') + assert.ok(invokedMessage1, 'Must have onMessage called for test to be valid.') + assert.ok(invokedMessage2, 'Must have onMessage2 called for test to be valid.') - t.equal(child._events.message[symbols.original], onMessage3) - t.end() + assert.equal(child._events.message[symbols.original], onMessage3) + end() }) }) }) -test('should not break multiple once() for multiple events down to multiple', (t) => { - const agent = setupAgent(t) +test('should not break multiple once() for multiple events down to multiple', (t, end) => { + const { agent } = t.nr helper.runInTransaction(agent, function () { const child = cp.fork('./exec-me.js', { cwd: __dirname }) @@ -189,20 +204,20 @@ test('should not break multiple once() for multiple events down to multiple', (t child.on('message', function onMessage4() {}) child.on('exit', function () { - t.ok(invokedMessage1, 'Must have onMessage called for test to be valid.') - t.ok(invokedMessage2, 'Must have onMessage2 called for test to be valid.') + assert.ok(invokedMessage1, 'Must have onMessage called for test to be valid.') + assert.ok(invokedMessage2, 'Must have onMessage2 called for test to be valid.') - t.ok(child._events.message) - t.equal(child._events.message.length, 2) + assert.ok(child._events.message) + assert.equal(child._events.message.length, 2) - t.end() + end() }) }) }) // Don't expect this should be possible but lets protect ourselves anyways. -test('should not break removal of non-wrapped listener', (t) => { - const agent = setupAgent(t) +test('should not break removal of non-wrapped listener', (t, end) => { + const { agent } = t.nr helper.runInTransaction(agent, function () { const child = cp.fork('./exec-me.js', { cwd: __dirname }) @@ -213,17 +228,17 @@ test('should not break removal of non-wrapped listener', (t) => { child.addListener('message', nonWrappedListener) child.removeListener('message', nonWrappedListener) - t.notOk(child._events.message) + assert.ok(!child._events.message) child.on('exit', function () { - t.end() + end() }) }) }) // Don't expect this should be possible but lets protect ourselves anyways. -test('should not break when non-wrapped listener exists', (t) => { - const agent = setupAgent(t) +test('should not break when non-wrapped listener exists', (t, end) => { + const { agent } = t.nr helper.runInTransaction(agent, function () { const child = cp.fork('./exec-me.js', { cwd: __dirname }) @@ -238,18 +253,18 @@ test('should not break when non-wrapped listener exists', (t) => { child.addListener('message', nonWrappedListener) child.on('exit', function () { - t.ok(invokedMessage, 'Must have onMessage called for test to be valid.') + assert.ok(invokedMessage, 'Must have onMessage called for test to be valid.') - t.ok(child._events.message) - t.equal(child._events.message, nonWrappedListener) + assert.ok(child._events.message) + assert.equal(child._events.message, nonWrappedListener) - t.end() + end() }) }) }) -test('should not introduce a new error nor hide error for missing handler', (t) => { - const agent = setupAgent(t) +test('should not introduce a new error nor hide error for missing handler', (t, end) => { + const { agent } = t.nr helper.runInTransaction(agent, function () { const child = cp.fork('./exec-me.js', { cwd: __dirname }) @@ -257,18 +272,18 @@ test('should not introduce a new error nor hide error for missing handler', (t) try { child.on('message', null) } catch (error) { - t.ok(error) - t.ok(error.message.includes('"listener" argument must be')) + assert.ok(error) + assert.ok(error.message.includes('"listener" argument must be')) } child.on('exit', function () { - t.end() + end() }) }) }) -test('should not introduce a new error nor hide error for invalid handler', (t) => { - const agent = setupAgent(t) +test('should not introduce a new error nor hide error for invalid handler', (t, end) => { + const { agent } = t.nr helper.runInTransaction(agent, function () { const child = cp.fork('./exec-me.js', { cwd: __dirname }) @@ -276,18 +291,18 @@ test('should not introduce a new error nor hide error for invalid handler', (t) try { child.on('message', 1) } catch (error) { - t.ok(error) - t.ok(error.message.includes('"listener" argument must be')) + assert.ok(error) + assert.ok(error.message.includes('"listener" argument must be')) } child.on('exit', function () { - t.end() + end() }) }) }) -test('should not break removeAllListeners', (t) => { - const agent = setupAgent(t) +test('should not break removeAllListeners', (t, end) => { + const { agent } = t.nr helper.runInTransaction(agent, function () { const child = cp.fork('./exec-me.js', { cwd: __dirname }) @@ -295,22 +310,13 @@ test('should not break removeAllListeners', (t) => { function onMessage() {} child.on('message', onMessage) - t.ok(child._events.message) + assert.ok(child._events.message) child.removeAllListeners('message') - t.notOk(child._events.message) + assert.ok(!child._events.message) child.on('exit', function () { - t.end() + end() }) }) }) - -function setupAgent(t) { - const agent = helper.instrumentMockedAgent() - t.teardown(function () { - helper.unloadAgent(agent) - }) - - return agent -} diff --git a/test/integration/core/crypto.tap.js b/test/integration/core/crypto.tap.js deleted file mode 100644 index 9e50557c72..0000000000 --- a/test/integration/core/crypto.tap.js +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const test = require('tap').test -const crypto = require('crypto') -const helper = require('../../lib/agent_helper') -const verifySegments = require('./verify.js') - -test('pbkdf2', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - crypto.pbkdf2('hunter2', 'saltine', 5, 32, 'sha1', function (err, key) { - t.notOk(err, 'should not error') - t.equal(key.length, 32) - verifySegments(t, agent, 'crypto.pbkdf2') - }) - }) -}) - -test('randomBytes', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - crypto.randomBytes(32, function (err, key) { - t.notOk(err, 'should not error') - t.ok(key.length, 32) - verifySegments(t, agent, 'crypto.randomBytes') - }) - }) -}) - -test('sync randomBytes', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function (transaction) { - const bytes = crypto.randomBytes(32) - t.ok(bytes instanceof Buffer) - t.equal(bytes.length, 32) - t.equal(transaction.trace.root.children.length, 0) - t.end() - }) -}) - -test('pseudoRandomBytes', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - // eslint-disable-next-line node/no-deprecated-api - crypto.pseudoRandomBytes(32, function (err, key) { - t.notOk(err, 'should not error') - t.ok(key.length, 32) - verifySegments(t, agent, 'crypto.pseudoRandomBytes') - }) - }) -}) - -test('sync pseudoRandomBytes', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function (transaction) { - // eslint-disable-next-line node/no-deprecated-api - const bytes = crypto.pseudoRandomBytes(32) - t.ok(bytes instanceof Buffer) - t.equal(bytes.length, 32) - t.equal(transaction.trace.root.children.length, 0) - t.end() - }) -}) - -test('randomFill', function (t) { - if (!crypto.randomFill) { - return t.end() - } - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - const buf = Buffer.alloc(10) - crypto.randomFill(buf, function (err, buffer) { - t.notOk(err, 'should not error') - t.ok(buffer.length, 10) - verifySegments(t, agent, 'crypto.randomFill') - }) - }) -}) - -test('sync randomFill', function (t) { - if (!crypto.randomFill) { - return t.end() - } - const agent = setupAgent(t) - helper.runInTransaction(agent, function (transaction) { - const buf = Buffer.alloc(10) - crypto.randomFillSync(buf) - t.ok(buf instanceof Buffer) - t.equal(buf.length, 10) - t.equal(transaction.trace.root.children.length, 0) - t.end() - }) -}) - -test('scrypt', (t) => { - if (!crypto.scrypt) { - return t.end() - } - const agent = setupAgent(t) - helper.runInTransaction(agent, () => { - crypto.scrypt('secret', 'salt', 10, (err, buf) => { - t.notOk(err, 'should not error') - t.ok(buf.length, 10) - verifySegments(t, agent, 'crypto.scrypt') - }) - }) -}) - -test('scryptSync', (t) => { - if (!crypto.scryptSync) { - return t.end() - } - const agent = setupAgent(t) - helper.runInTransaction(agent, (transaction) => { - const buf = crypto.scryptSync('secret', 'salt', 10) - t.ok(buf instanceof Buffer) - t.equal(buf.length, 10) - t.equal(transaction.trace.root.children.length, 0) - t.end() - }) -}) - -function setupAgent(t) { - const agent = helper.instrumentMockedAgent() - t.teardown(function () { - helper.unloadAgent(agent) - }) - - return agent -} diff --git a/test/integration/core/crypto.test.js b/test/integration/core/crypto.test.js new file mode 100644 index 0000000000..e9845463d6 --- /dev/null +++ b/test/integration/core/crypto.test.js @@ -0,0 +1,124 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const crypto = require('crypto') +const helper = require('../../lib/agent_helper') +const verifySegments = require('./verify.js') + +test.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.instrumentMockedAgent() +}) + +test.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) +}) + +test('pbkdf2', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + crypto.pbkdf2('hunter2', 'saltine', 5, 32, 'sha1', function (err, key) { + assert.ok(!err, 'should not error') + assert.equal(key.length, 32) + verifySegments({ agent, end, name: 'crypto.pbkdf2' }) + }) + }) +}) + +test('randomBytes', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + crypto.randomBytes(32, function (err, key) { + assert.ok(!err, 'should not error') + assert.ok(key.length, 32) + verifySegments({ agent, end, name: 'crypto.randomBytes' }) + }) + }) +}) + +test('sync randomBytes', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function (transaction) { + const bytes = crypto.randomBytes(32) + assert.ok(bytes instanceof Buffer) + assert.equal(bytes.length, 32) + assert.equal(transaction.trace.root.children.length, 0) + end() + }) +}) + +test('pseudoRandomBytes', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + // eslint-disable-next-line node/no-deprecated-api + crypto.pseudoRandomBytes(32, function (err, key) { + assert.ok(!err, 'should not error') + assert.ok(key.length, 32) + verifySegments({ agent, end, name: 'crypto.pseudoRandomBytes' }) + }) + }) +}) + +test('sync pseudoRandomBytes', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function (transaction) { + // eslint-disable-next-line node/no-deprecated-api + const bytes = crypto.pseudoRandomBytes(32) + assert.ok(bytes instanceof Buffer) + assert.equal(bytes.length, 32) + assert.equal(transaction.trace.root.children.length, 0) + end() + }) +}) + +test('randomFill', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + const buf = Buffer.alloc(10) + crypto.randomFill(buf, function (err, buffer) { + assert.ok(!err, 'should not error') + assert.ok(buffer.length, 10) + verifySegments({ agent, end, name: 'crypto.randomFill' }) + }) + }) +}) + +test('sync randomFill', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function (transaction) { + const buf = Buffer.alloc(10) + crypto.randomFillSync(buf) + assert.ok(buf instanceof Buffer) + assert.equal(buf.length, 10) + assert.equal(transaction.trace.root.children.length, 0) + end() + }) +}) + +test('scrypt', (t, end) => { + const { agent } = t.nr + helper.runInTransaction(agent, () => { + crypto.scrypt('secret', 'salt', 10, (err, buf) => { + assert.ok(!err, 'should not error') + assert.ok(buf.length, 10) + verifySegments({ agent, end, name: 'crypto.scrypt' }) + }) + }) +}) + +test('scryptSync', (t, end) => { + const { agent } = t.nr + helper.runInTransaction(agent, (transaction) => { + const buf = crypto.scryptSync('secret', 'salt', 10) + assert.ok(buf instanceof Buffer) + assert.equal(buf.length, 10) + assert.equal(transaction.trace.root.children.length, 0) + end() + }) +}) diff --git a/test/integration/core/dns.tap.js b/test/integration/core/dns.tap.js deleted file mode 100644 index 232aa8c16d..0000000000 --- a/test/integration/core/dns.tap.js +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const test = require('tap').test -const dns = require('dns') -const helper = require('../../lib/agent_helper') -const verifySegments = require('./verify.js') - -test('lookup - IPv4', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - dns.lookup('localhost', { verbatim: false }, function (err, ip, v) { - t.notOk(err, 'should not error') - t.equal(ip, '127.0.0.1') - t.equal(v, 4) - verifySegments(t, agent, 'dns.lookup') - }) - }) -}) - -test('lookup - IPv6', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - // Verbatim defaults to true in Node 18+ - dns.lookup('localhost', { verbatim: true }, function (err, ip, v) { - t.notOk(err, 'should not error') - t.equal(ip, '::1') - t.equal(v, 6) - verifySegments(t, agent, 'dns.lookup') - }) - }) -}) - -test('resolve', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - dns.resolve('example.com', function (err, ips) { - t.notOk(err, 'should not error') - t.equal(ips.length, 1) - t.ok(ips[0].match(/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/)) - - const children = [] - verifySegments(t, agent, 'dns.resolve', children) - }) - }) -}) - -test('resolve4', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - dns.resolve4('example.com', function (err, ips) { - t.notOk(err, 'should not error') - t.equal(ips.length, 1) - t.ok(ips[0].match(/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/)) - verifySegments(t, agent, 'dns.resolve4') - }) - }) -}) - -test('resolve6', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - dns.resolve6('example.com', function (err, ips) { - t.notOk(err, 'should not error') - t.equal(ips.length, 1) - t.ok(ips[0].match(/^(([0-9a-f]{1,4})(\:|$)){8}/)) - verifySegments(t, agent, 'dns.resolve6') - }) - }) -}) - -test('resolveCname', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - dns.resolveCname('example.com', function (err) { - t.equal(err.code, 'ENODATA') - verifySegments(t, agent, 'dns.resolveCname') - }) - }) -}) - -test('resolveMx', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - dns.resolveMx('example.com', function (err, ips) { - t.notOk(err) - t.equal(ips.length, 1) - - verifySegments(t, agent, 'dns.resolveMx') - }) - }) -}) - -test('resolveNs', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - dns.resolveNs('example.com', function (err, names) { - t.notOk(err, 'should not error') - t.same(names.sort(), ['a.iana-servers.net', 'b.iana-servers.net']) - verifySegments(t, agent, 'dns.resolveNs') - }) - }) -}) - -test('resolveTxt', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - dns.resolveTxt('example.com', function (err, data) { - t.notOk(err) - t.ok(Array.isArray(data)) - verifySegments(t, agent, 'dns.resolveTxt') - }) - }) -}) - -test('resolveSrv', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - dns.resolveSrv('example.com', function (err) { - t.equal(err.code, 'ENODATA') - verifySegments(t, agent, 'dns.resolveSrv') - }) - }) -}) - -test('reverse', function (t) { - const reverse = dns.reverse - dns.reverse = (addr, cb) => { - cb(undefined, ['localhost']) - } - t.teardown(() => { - dns.reverse = reverse - }) - - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - dns.reverse('127.0.0.1', function (err, names) { - t.error(err, 'should not error') - t.not(names.indexOf('localhost'), -1, 'should have expected name') - verifySegments(t, agent, 'dns.reverse') - }) - }) -}) - -function setupAgent(t) { - const agent = helper.instrumentMockedAgent() - t.teardown(function () { - helper.unloadAgent(agent) - }) - - return agent -} diff --git a/test/integration/core/dns.test.js b/test/integration/core/dns.test.js new file mode 100644 index 0000000000..4739428066 --- /dev/null +++ b/test/integration/core/dns.test.js @@ -0,0 +1,155 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const dns = require('dns') +const helper = require('../../lib/agent_helper') +const verifySegments = require('./verify.js') + +test.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.reverse = dns.reverse + // wrap dns.reverse to not try to actually execute this function + dns.reverse = (addr, cb) => { + cb(undefined, ['localhost']) + } + ctx.nr.agent = helper.instrumentMockedAgent() +}) + +test.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + dns.reverse = ctx.nr.reverse +}) + +test('lookup - IPv4', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + dns.lookup('localhost', { verbatim: false }, function (err, ip, v) { + assert.ok(!err, 'should not error') + assert.equal(ip, '127.0.0.1') + assert.equal(v, 4) + verifySegments({ agent, end, name: 'dns.lookup' }) + }) + }) +}) + +test('lookup - IPv6', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + // Verbatim defaults to true in Node 18+ + dns.lookup('localhost', { verbatim: true }, function (err, ip, v) { + assert.ok(!err, 'should not error') + assert.equal(ip, '::1') + assert.equal(v, 6) + verifySegments({ agent, end, name: 'dns.lookup' }) + }) + }) +}) + +test('resolve', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + dns.resolve('example.com', function (err, ips) { + assert.ok(!err, 'should not error') + assert.equal(ips.length, 1) + assert.ok(ips[0].match(/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/)) + + const children = [] + verifySegments({ agent, end, name: 'dns.resolve', children }) + }) + }) +}) + +test('resolve4', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + dns.resolve4('example.com', function (err, ips) { + assert.ok(!err, 'should not error') + assert.equal(ips.length, 1) + assert.ok(ips[0].match(/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/)) + verifySegments({ agent, end, name: 'dns.resolve4' }) + }) + }) +}) + +test('resolve6', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + dns.resolve6('example.com', function (err, ips) { + assert.ok(!err, 'should not error') + assert.equal(ips.length, 1) + assert.ok(ips[0].match(/^(([0-9a-f]{1,4})(\:|$)){8}/)) + verifySegments({ agent, end, name: 'dns.resolve6' }) + }) + }) +}) + +test('resolveCname', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + dns.resolveCname('example.com', function (err) { + assert.equal(err.code, 'ENODATA') + verifySegments({ agent, end, name: 'dns.resolveCname' }) + }) + }) +}) + +test('resolveMx', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + dns.resolveMx('example.com', function (err, ips) { + assert.ok(!err, 'should not error') + assert.equal(ips.length, 1) + + verifySegments({ agent, end, name: 'dns.resolveMx' }) + }) + }) +}) + +test('resolveNs', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + dns.resolveNs('example.com', function (err, names) { + assert.ok(!err, 'should not error') + assert.deepEqual(names.sort(), ['a.iana-servers.net', 'b.iana-servers.net']) + verifySegments({ agent, end, name: 'dns.resolveNs' }) + }) + }) +}) + +test('resolveTxt', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + dns.resolveTxt('example.com', function (err, data) { + assert.ok(!err, 'should not error') + assert.ok(Array.isArray(data)) + verifySegments({ agent, end, name: 'dns.resolveTxt' }) + }) + }) +}) + +test('resolveSrv', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + dns.resolveSrv('example.com', function (err) { + assert.equal(err.code, 'ENODATA') + verifySegments({ agent, end, name: 'dns.resolveSrv' }) + }) + }) +}) + +test('reverse', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + dns.reverse('127.0.0.1', function (err, names) { + assert.ok(!err, 'should not error') + assert.ok(names.indexOf('localhost') !== -1, 'should have expected name') + verifySegments({ agent, end, name: 'dns.reverse' }) + }) + }) +}) diff --git a/test/integration/core/exceptions.tap.js b/test/integration/core/exceptions.test.js similarity index 56% rename from test/integration/core/exceptions.tap.js rename to test/integration/core/exceptions.test.js index d61a504168..489575cc03 100644 --- a/test/integration/core/exceptions.tap.js +++ b/test/integration/core/exceptions.test.js @@ -4,53 +4,51 @@ */ 'use strict' - -const tap = require('tap') +const test = require('node:test') +const { tspl } = require('@matteo.collina/tspl') const cp = require('child_process') const path = require('path') const helper = require('../../lib/agent_helper') const helpersDir = path.join(path.resolve(__dirname, '../../'), 'helpers') -tap.test('Uncaught exceptions', (t) => { +test('Uncaught exceptions', async (t) => { + const plan = tspl(t, { plan: 1 }) const proc = startProc() const timer = setTimeout(function () { - t.fail('child did not exit') proc.kill() - t.end() }, 10000) proc.on('exit', function () { - t.ok(true, 'Did not timeout') + plan.ok(1, 'Did not timeout') clearTimeout(timer) - t.end() }) proc.send({ name: 'uncaughtException' }) + await plan.completed }) -tap.test('Caught uncaught exceptions', (t) => { +test('Caught uncaught exceptions', async (t) => { + const plan = tspl(t, { plan: 1 }) const proc = startProc() const theRightStuff = 31415927 const timer = setTimeout(function () { - t.fail('child hung') proc.kill() - t.end() }, 10000) proc.on('message', function (code) { - t.equal(parseInt(code, 10), theRightStuff, 'should have the correct code') + plan.equal(parseInt(code, 10), theRightStuff, 'should have the correct code') clearTimeout(timer) proc.kill() - t.end() }) proc.send({ name: 'caughtUncaughtException', args: theRightStuff }) + await plan.completed }) -tap.test('Report uncaught exceptions', (t) => { - t.plan(3) +test('Report uncaught exceptions', async (t) => { + const plan = tspl(t, { plan: 3 }) const proc = startProc() const message = 'I am a test error' @@ -58,21 +56,21 @@ tap.test('Report uncaught exceptions', (t) => { proc.on('message', function (errors) { messageReceived = true - t.equal(errors.count, 1, 'should have collected an error') - t.equal(errors.messages[0], message, 'should have the correct message') + plan.equal(errors.count, 1, 'should have collected an error') + plan.equal(errors.messages[0], message, 'should have the correct message') proc.kill() }) proc.on('exit', function () { - t.ok(messageReceived, 'should receive message') - t.end() + plan.ok(messageReceived, 'should receive message') }) proc.send({ name: 'checkAgent', args: message }) + await plan.completed }) -tap.test('Triggers harvest while in serverless mode', (t) => { - t.plan(9) +test('Triggers harvest while in serverless mode', async (t) => { + const plan = tspl(t, { plan: 9 }) const proc = startProc({ NEW_RELIC_SERVERLESS_MODE_ENABLED: 'y', @@ -89,94 +87,94 @@ tap.test('Triggers harvest while in serverless mode', (t) => { proc.on('message', function (errors) { messageReceived = true - t.equal(errors.count, 0, 'should have harvested the error') + plan.equal(errors.count, 0, 'should have harvested the error') const lambdaPayload = findLambdaPayload(payload) - t.ok(lambdaPayload, 'should find lambda payload log line') + plan.ok(lambdaPayload, 'should find lambda payload log line') const parsed = JSON.parse(lambdaPayload) helper.decodeServerlessPayload(t, parsed[2], function testDecoded(err, decoded) { - t.error(err, 'should not run into errors decoding serverless payload') - t.ok(decoded.metadata, 'metadata should be present') - t.ok(decoded.data, 'data should be present') + plan.ok(!err, 'should not run into errors decoding serverless payload') + plan.ok(decoded.metadata, 'metadata should be present') + plan.ok(decoded.data, 'data should be present') const error = decoded.data.error_data[1][0] - t.equal(error[2], message) + plan.equal(error[2], message) const transactionEvents = decoded.data.analytic_event_data - t.ok(transactionEvents, 'should have a transaction event') + plan.ok(transactionEvents, 'should have a transaction event') const transactionEvent = transactionEvents[2][0] - t.ok(transactionEvent[0].error, 'should be errored') + plan.ok(transactionEvent[0].error, 'should be errored') proc.kill() }) }) proc.on('exit', function () { - t.ok(messageReceived, 'should receive message') - t.end() + plan.ok(messageReceived, 'should receive message') }) proc.send({ name: 'runServerlessTransaction', args: message }) + await plan.completed }) -tap.test('Do not report domained exceptions', (t) => { - t.plan(3) +test('Do not report domained exceptions', async (t) => { + const plan = tspl(t, { plan: 3 }) const proc = startProc() const message = 'I am a test error' let messageReceived = false proc.on('message', function (errors) { messageReceived = true - t.equal(errors.count, 0, 'should not have collected an error') - t.same(errors.messages, [], 'should have no error messages') + plan.equal(errors.count, 0, 'should not have collected an error') + plan.deepEqual(errors.messages, [], 'should have no error messages') proc.kill() }) proc.on('exit', function () { - t.ok(messageReceived, 'should receive message') - t.end() + plan.ok(messageReceived, 'should receive message') }) proc.send({ name: 'domainUncaughtException', args: message }) + await plan.completed }) -tap.test('Report exceptions handled in setUncaughtExceptionCaptureCallback', (t) => { - t.plan(3) +test('Report exceptions handled in setUncaughtExceptionCaptureCallback', async (t) => { + const plan = tspl(t, { plan: 3 }) const proc = startProc() let messageReceived = false proc.on('message', (errors) => { messageReceived = true - t.equal(errors.count, 0, 'should not have collected an error') - t.same(errors.messages, [], 'should have no error messages') + plan.equal(errors.count, 0, 'should not have collected an error') + plan.deepEqual(errors.messages, [], 'should have no error messages') proc.kill() }) proc.on('exit', () => { - t.ok(messageReceived, 'should receive message') - t.end() + plan.ok(messageReceived, 'should receive message') }) proc.send({ name: 'setUncaughtExceptionCallback' }) + await plan.completed }) -tap.test('Report exceptions handled in setUncaughtExceptionCaptureCallback', (t) => { - t.plan(3) +test('Report exceptions handled in setUncaughtExceptionCaptureCallback', async (t) => { + const plan = tspl(t, { plan: 3 }) const proc = startProc() let messageReceived = false proc.on('message', (errors) => { messageReceived = true - t.equal(errors.count, 1, 'should have collected an error') - t.same(errors.messages, ['nothing can keep me down'], 'should have error messages') + plan.equal(errors.count, 1, 'should have collected an error') + plan.deepEqual(errors.messages, ['nothing can keep me down'], 'should have error messages') proc.kill() }) proc.on('exit', () => { - t.ok(messageReceived, 'should receive message') - t.end() + plan.ok(messageReceived, 'should receive message') }) proc.send({ name: 'unsetUncaughtExceptionCallback' }) + await plan.completed }) function startProc(env) { diff --git a/test/integration/core/fs.tap.js b/test/integration/core/fs.tap.js deleted file mode 100644 index 9b58727297..0000000000 --- a/test/integration/core/fs.tap.js +++ /dev/null @@ -1,871 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const tapTest = require('tap').test -const path = require('path') -const temp = require('temp') -const fs = require('fs') -const helper = require('../../lib/agent_helper') -const verifySegments = require('./verify') -const NAMES = require('../../../lib/metrics/names') - -const isGlobSupported = require('semver').satisfies(process.version, '>=22.0.0') - -// delete temp files before process exits -temp.track() - -const tempDir = temp.mkdirSync('fs-tests') - -// Set umask before and after fs tests (for normalizing create mode on OS X and linux) -const mask = process.umask('0000') -let tasks = 0 -let done = 0 - -// Because of how async all these tests are, and that they interact with a something slow -// like the filesystem, there were problems with them timing out in aggregate. Doing this, -// rather than using a parent test avoids this problem. Node-tap was also causing problems -// with this set of tests. -function test(title, options, callback) { - if (typeof options === 'function') { - callback = options - options = {} - } - - if (!options.skip) { - tasks++ - } - options.timeout = 15000 // Allow for a slow file system. - tapTest(title, options, function (t) { - t.teardown(function () { - if (++done === tasks) { - process.umask(mask) - } - }) - - callback.apply(this, arguments) - }) -} - -function checkMetric(names, agent, scope) { - let res = true - const agentMetrics = getMetrics(agent) - const metrics = scope ? agentMetrics.scoped[scope] : agentMetrics.unscoped - names.forEach((name) => { - res = res && metrics[NAMES.FS.PREFIX + name] - }) - return res -} - -test('rename', function (t) { - const name = path.join(tempDir, 'rename-me') - const newName = path.join(tempDir, 'renamed') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - fs.rename(name, newName, function (err) { - t.notOk(err, 'should not error') - helper.unloadAgent(agent) - t.ok(fs.existsSync(newName), 'file with new name should exist') - t.notOk(fs.existsSync(name), 'file with old name should not exist') - t.equal( - fs.readFileSync(newName, 'utf8'), - content, - 'file with new name should have expected contents' - ) - verifySegments(t, agent, NAMES.FS.PREFIX + 'rename') - - trans.end() - t.ok(checkMetric(['rename'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('truncate', function (t) { - const name = path.join(tempDir, 'truncate-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - // if fs.ftruncate isn't around, it means that fs.truncate uses a file descriptor - // rather than a path, and won't trigger an 'open' segment due to implementation - // differences. This is mostly just a version check for v0.8. - const expectedSegments = ['open', 'truncate'] - fs.truncate(name, 4, function (err) { - t.notOk(err, 'should not error') - helper.unloadAgent(agent) - t.equal(fs.readFileSync(name, 'utf8'), content.slice(0, 4), 'content should be truncated') - verifySegments(t, agent, NAMES.FS.PREFIX + 'truncate', [NAMES.FS.PREFIX + 'open']) - - trans.end() - t.ok( - checkMetric(expectedSegments, agent, trans.name), - 'metric should exist after transaction end' - ) - }) - }) -}) - -test('ftruncate', function (t) { - const name = path.join(tempDir, 'ftruncate-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const fd = fs.openSync(name, 'r+') - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - fs.ftruncate(fd, 4, function (err) { - t.notOk(err, 'should not error') - helper.unloadAgent(agent) - t.equal(fs.readFileSync(name, 'utf8'), content.slice(0, 4), 'content should be truncated') - verifySegments(t, agent, NAMES.FS.PREFIX + 'ftruncate') - - trans.end() - t.ok( - checkMetric(['ftruncate'], agent, trans.name), - 'metric should exist after transaction end' - ) - }) - }) -}) - -test('chown', function (t) { - const name = path.join(tempDir, 'chown-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - const uid = 0 - const gid = 0 - helper.runInTransaction(agent, function (trans) { - fs.chown(name, uid, gid, function (err) { - t.ok(err, 'should error for non root users') - verifySegments(t, agent, NAMES.FS.PREFIX + 'chown') - - trans.end() - t.ok(checkMetric(['chown'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('fchown', function (t) { - const name = path.join(tempDir, 'chown-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const fd = fs.openSync(name, 'r+') - const agent = setupAgent(t) - const uid = 0 - const gid = 0 - helper.runInTransaction(agent, function (trans) { - fs.fchown(fd, uid, gid, function (err) { - t.ok(err, 'should error for non root users') - verifySegments(t, agent, NAMES.FS.PREFIX + 'fchown') - - trans.end() - t.ok(checkMetric(['fchown'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -// Only exists on Darwin currently, using this check to catch if it -// appears in other versions too. -test('lchown', { skip: fs.lchown === undefined }, function (t) { - const name = path.join(tempDir, 'chown-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - const uid = 0 - const gid = 0 - helper.runInTransaction(agent, function (trans) { - fs.lchown(name, uid, gid, function (err) { - t.ok(err, 'should error for non root users') - verifySegments(t, agent, NAMES.FS.PREFIX + 'lchown') - - trans.end() - const names = ['lchown'] - t.ok(checkMetric(names, agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('chmod', function (t) { - const name = path.join(tempDir, 'chmod-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - t.equal((fs.statSync(name).mode & 0x1ff).toString(8), '666') - helper.runInTransaction(agent, function (trans) { - fs.chmod(name, '0777', function (err) { - t.equal(err, null, 'should not error') - helper.unloadAgent(agent) - t.equal((fs.statSync(name).mode & 0x1ff).toString(8), '777') - verifySegments(t, agent, NAMES.FS.PREFIX + 'chmod') - - trans.end() - t.ok(checkMetric(['chmod'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -// Only exists on Darwin currently, using this check to catch if it -// appears in other versions too. -// eslint-disable-next-line node/no-deprecated-api -test('lchmod', { skip: fs.lchmod === undefined }, function (t) { - const name = path.join(tempDir, 'lchmod-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - t.equal((fs.statSync(name).mode & 0x1ff).toString(8), '666') - helper.runInTransaction(agent, function (trans) { - // eslint-disable-next-line node/no-deprecated-api - fs.lchmod(name, '0777', function (err) { - t.equal(err, null, 'should not error') - helper.unloadAgent(agent) - t.equal((fs.statSync(name).mode & 0x1ff).toString(8), '777') - verifySegments(t, agent, NAMES.FS.PREFIX + 'lchmod', [NAMES.FS.PREFIX + 'open']) - - trans.end() - t.ok( - checkMetric(['lchmod', 'open'], agent, trans.name), - 'metric should exist after transaction end' - ) - }) - }) -}) - -test('fchmod', function (t) { - const name = path.join(tempDir, 'fchmod-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const fd = fs.openSync(name, 'r+') - const agent = setupAgent(t) - t.equal((fs.statSync(name).mode & 0x1ff).toString(8), '666') - helper.runInTransaction(agent, function (trans) { - fs.fchmod(fd, '0777', function (err) { - t.equal(err, null, 'should not error') - helper.unloadAgent(agent) - t.equal((fs.statSync(name).mode & 0x1ff).toString(8), '777') - verifySegments(t, agent, NAMES.FS.PREFIX + 'fchmod') - - trans.end() - t.ok(checkMetric(['fchmod'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('stat', function (t) { - const name = path.join(tempDir, 'stat-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - fs.stat(name, function (err, stat) { - t.equal(err, null, 'should not error') - t.equal((stat.mode & 0x1ff).toString(8), '666') - verifySegments(t, agent, NAMES.FS.PREFIX + 'stat') - - trans.end() - t.ok(checkMetric(['stat'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('lstat', function (t) { - const name = path.join(tempDir, 'lstat-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - fs.lstat(name, function (err, stat) { - t.equal(err, null, 'should not error') - t.equal((stat.mode & 0x1ff).toString(8), '666') - verifySegments(t, agent, NAMES.FS.PREFIX + 'lstat') - - trans.end() - t.ok(checkMetric(['lstat'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('fstat', function (t) { - const name = path.join(tempDir, 'fstat-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const fd = fs.openSync(name, 'r+') - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - fs.fstat(fd, function (err, stat) { - t.equal(err, null, 'should not error') - t.equal((stat.mode & 0x1ff).toString(8), '666') - verifySegments(t, agent, NAMES.FS.PREFIX + 'fstat') - - trans.end() - t.ok(checkMetric(['fstat'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('link', function (t) { - const name = path.join(tempDir, 'link-to-me') - const link = path.join(tempDir, 'link-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - fs.link(name, link, function (err) { - t.equal(err, null, 'should not error') - t.equal(fs.statSync(name).ino, fs.statSync(link).ino, 'should point to the same file') - - verifySegments(t, agent, NAMES.FS.PREFIX + 'link') - - trans.end() - t.ok(checkMetric(['link'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('symlink', function (t) { - const name = path.join(tempDir, 'symlink-to-me') - const link = path.join(tempDir, 'symlink-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - fs.symlink(name, link, function (err) { - t.equal(err, null, 'should not error') - t.equal(fs.readlinkSync(link), name, 'should point to the same file') - - verifySegments(t, agent, NAMES.FS.PREFIX + 'symlink') - - trans.end() - t.ok(checkMetric(['symlink'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('readlink', function (t) { - const name = path.join(tempDir, 'readlink') - const link = path.join(tempDir, 'readlink-me') - const content = 'some-content' - fs.writeFileSync(name, content) - fs.symlinkSync(name, link) - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - fs.readlink(link, function (err, target) { - t.equal(err, null, 'should not error') - t.equal(target, name, 'should point to the same file') - - verifySegments(t, agent, NAMES.FS.PREFIX + 'readlink') - - trans.end() - t.ok( - checkMetric(['readlink'], agent, trans.name), - 'metric should exist after transaction end' - ) - }) - }) -}) - -test('realpath', function (t) { - const name = path.join(tempDir, 'realpath') - const link = path.join(tempDir, 'link-to-realpath') - const content = 'some-content' - fs.writeFileSync(name, content) - fs.symlinkSync(name, link) - const real = fs.realpathSync(name) - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - fs.realpath(link, function (err, target) { - t.equal(err, null, 'should not error') - t.equal(target, real, 'should point to the same file') - - verifySegments( - t, - agent, - NAMES.FS.PREFIX + 'realpath', - [NAMES.FS.PREFIX + 'lstat'], - afterVerify - ) - - function afterVerify() { - trans.end() - const expectedMetrics = ['realpath'] - t.ok( - checkMetric(expectedMetrics, agent, trans.name), - 'metric should exist after transaction end' - ) - t.end() - } - }) - }) -}) - -test('realpath.native', (t) => { - if (!fs.realpath.native) { - return t.end() - } - const name = path.join(tempDir, 'realpath-native') - const link = path.join(tempDir, 'link-to-realpath-native') - const content = 'some-content' - fs.writeFileSync(name, content) - fs.symlinkSync(name, link) - const real = fs.realpathSync(name) - const agent = setupAgent(t) - helper.runInTransaction(agent, (trans) => { - fs.realpath.native(link, (err, target) => { - t.equal(err, null, 'should not error') - t.equal(target, real, 'should point to the same file') - - verifySegments(t, agent, NAMES.FS.PREFIX + 'realpath.native', afterVerify) - - function afterVerify() { - trans.end() - const expectedMetrics = ['realpath.native'] - t.ok( - checkMetric(expectedMetrics, agent, trans.name), - 'metric should exist after transaction end' - ) - t.end() - } - }) - }) -}) - -test('unlink', function (t) { - const name = path.join(tempDir, 'unlink-from-me') - const link = path.join(tempDir, 'unlink-me') - const content = 'some-content' - fs.writeFileSync(name, content) - fs.symlinkSync(name, link) - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - fs.unlink(link, function (err) { - t.equal(err, null, 'should not error') - t.notOk(fs.existsSync(link), 'link should not exist') - verifySegments(t, agent, NAMES.FS.PREFIX + 'unlink') - - trans.end() - t.ok(checkMetric(['unlink'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('mkdir', function (t) { - const name = path.join(tempDir, 'mkdir') - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - fs.mkdir(name, function (err) { - t.equal(err, null, 'should not error') - t.ok(fs.existsSync(name), 'dir should exist') - t.ok(fs.readdirSync(name), 'dir should be readable') - verifySegments(t, agent, NAMES.FS.PREFIX + 'mkdir') - - trans.end() - t.ok(checkMetric(['mkdir'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('rmdir', function (t) { - const name = path.join(tempDir, 'rmdir') - const agent = setupAgent(t) - fs.mkdirSync(name) - helper.runInTransaction(agent, function (trans) { - fs.rmdir(name, function (err) { - t.equal(err, null, 'should not error') - t.notOk(fs.existsSync(name), 'dir should not exist') - verifySegments(t, agent, NAMES.FS.PREFIX + 'rmdir') - - trans.end() - t.ok(checkMetric(['rmdir'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('readdir', function (t) { - const name = path.join(tempDir, 'readdir') - const agent = setupAgent(t) - fs.mkdirSync(name) - helper.runInTransaction(agent, function (trans) { - fs.readdir(name, function (err, data) { - t.equal(err, null, 'should not error') - t.same(data, [], 'should get list of contents') - verifySegments(t, agent, NAMES.FS.PREFIX + 'readdir') - - trans.end() - t.ok(checkMetric(['readdir'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('close', function (t) { - const name = path.join(tempDir, 'close-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const fd = fs.openSync(name, 'r+') - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - fs.close(fd, function (err) { - t.equal(err, null, 'should not error') - verifySegments(t, agent, NAMES.FS.PREFIX + 'close') - - trans.end() - t.ok(checkMetric(['close'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('open', function (t) { - const name = path.join(tempDir, 'open-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - helper.runInTransaction(agent, function (trans) { - fs.open(name, 'r+', function (err, fd) { - t.equal(err, null, 'should not error') - t.ok(fd, 'should get a file descriptor') - verifySegments(t, agent, NAMES.FS.PREFIX + 'open') - - trans.end() - t.ok(checkMetric(['open'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('utimes', function (t) { - const name = path.join(tempDir, 'utimes-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - const accessed = 5 - const modified = 15 - - helper.runInTransaction(agent, function (trans) { - fs.utimes(name, accessed, modified, function (err) { - t.notOk(err, 'should not error') - const stats = fs.statSync(name) - t.equal(stats.atime.toISOString(), '1970-01-01T00:00:05.000Z') - t.equal(stats.mtime.toISOString(), '1970-01-01T00:00:15.000Z') - verifySegments(t, agent, NAMES.FS.PREFIX + 'utimes') - - trans.end() - t.ok(checkMetric(['utimes'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('futimes', function (t) { - const name = path.join(tempDir, 'futimes-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const fd = fs.openSync(name, 'r+') - const agent = setupAgent(t) - const accessed = 5 - const modified = 15 - - helper.runInTransaction(agent, function (trans) { - fs.futimes(fd, accessed, modified, function (err) { - t.notOk(err, 'should not error') - const stats = fs.statSync(name) - t.equal(stats.atime.toISOString(), '1970-01-01T00:00:05.000Z') - t.equal(stats.mtime.toISOString(), '1970-01-01T00:00:15.000Z') - verifySegments(t, agent, NAMES.FS.PREFIX + 'futimes') - - trans.end() - t.ok(checkMetric(['futimes'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('fsync', function (t) { - const name = path.join(tempDir, 'fsync-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const fd = fs.openSync(name, 'r+') - const agent = setupAgent(t) - - helper.runInTransaction(agent, function (trans) { - fs.fsync(fd, function (err) { - t.notOk(err, 'should not error') - verifySegments(t, agent, NAMES.FS.PREFIX + 'fsync') - - trans.end() - t.ok(checkMetric(['fsync'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('readFile', function (t) { - const name = path.join(tempDir, 'readFile') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - - helper.runInTransaction(agent, function (trans) { - fs.readFile(name, function (err, data) { - t.notOk(err, 'should not error') - t.equal(data.toString('utf8'), content) - - let expectFSOpen = true - // io.js changed their implementation of fs.readFile to use process.binding. - // This caused the file opening not to be added to the trace when using io.js. - // By checking this value, we can determine whether or not to expect it. - if (agent.getTransaction().trace.root.children[0].children.length === 1) { - expectFSOpen = false - } - verifySegments( - t, - agent, - NAMES.FS.PREFIX + 'readFile', - expectFSOpen ? [NAMES.FS.PREFIX + 'open'] : [] - ) - - trans.end() - t.ok( - checkMetric(expectFSOpen ? ['open', 'readFile'] : ['readFile'], agent, trans.name), - 'metric should exist after transaction end' - ) - }) - }) -}) - -test('writeFile', function (t) { - const name = path.join(tempDir, 'writeFile') - const content = 'some-content' - const agent = setupAgent(t) - - helper.runInTransaction(agent, function (trans) { - fs.writeFile(name, content, function (err) { - t.notOk(err, 'should not error') - t.equal(fs.readFileSync(name).toString('utf8'), content) - verifySegments(t, agent, NAMES.FS.PREFIX + 'writeFile', [NAMES.FS.PREFIX + 'open']) - - trans.end() - t.ok( - checkMetric(['writeFile', 'open'], agent, trans.name), - 'metric should exist after transaction end' - ) - }) - }) -}) - -test('appendFile', function (t) { - const name = path.join(tempDir, 'appendFile') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - const expectedSegments = ['appendFile', 'writeFile'] - - helper.runInTransaction(agent, function (trans) { - fs.appendFile(name, '123', function (err) { - t.notOk(err, 'should not error') - t.equal(fs.readFileSync(name).toString('utf-8'), content + '123') - verifySegments(t, agent, NAMES.FS.PREFIX + 'appendFile', [NAMES.FS.PREFIX + 'writeFile']) - - trans.end() - t.ok( - checkMetric(expectedSegments, agent, trans.name), - 'metric should exist after transaction end' - ) - }) - }) -}) - -test('exists', function (t) { - const name = path.join(tempDir, 'exists') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - - helper.runInTransaction(agent, function (trans) { - // eslint-disable-next-line node/no-deprecated-api - fs.exists(name, function (exists) { - t.ok(exists, 'should exist') - verifySegments(t, agent, NAMES.FS.PREFIX + 'exists') - - trans.end() - t.ok(checkMetric(['exists'], agent, trans.name), 'metric should exist after transaction end') - }) - }) -}) - -test('read', function (t) { - const name = path.join(tempDir, 'read') - const content = 'some-content' - fs.writeFileSync(name, content) - const fd = fs.openSync(name, 'r+') - const agent = setupAgent(t) - const buf = Buffer.alloc(content.length) - - helper.runInTransaction(agent, function (trans) { - fs.read(fd, buf, 0, content.length, 0, function (err, len, data) { - t.notOk(err, 'should not error') - t.equal(len, 12, 'should read correct number of bytes') - t.equal(data.toString('utf8'), content) - t.equal(agent.getTransaction(), trans, 'should preserve transaction') - t.equal(trans.trace.root.children.length, 0, 'should not create any segments') - t.end() - }) - }) -}) - -test('write', function (t) { - const name = path.join(tempDir, 'write') - const content = 'some-content' - fs.writeFileSync(name, '') - const fd = fs.openSync(name, 'r+') - const agent = setupAgent(t) - const buf = Buffer.from(content) - - helper.runInTransaction(agent, function (trans) { - fs.write(fd, buf, 0, content.length, 0, function (err, len) { - t.notOk(err, 'should not error') - t.equal(len, 12, 'should write correct number of bytes') - t.equal(fs.readFileSync(name, 'utf8'), content) - t.equal(agent.getTransaction(), trans, 'should preserve transaction') - t.equal(trans.trace.root.children.length, 0, 'should not create any segments') - t.end() - }) - }) -}) - -test('watch (file)', function (t) { - t.plan(5) - - const name = path.join(tempDir, 'watch-file') - const content = 'some-content' - const agent = setupAgent(t) - fs.writeFileSync(name, content) - - setTimeout(function () { - helper.runInTransaction(agent, function (trans) { - const watcher = fs.watch(name, function (ev, file) { - t.equal(ev, 'change', 'should be expected event') - - t.equal(file, 'watch-file', 'should have correct file name') - t.equal(agent.getTransaction(), trans, 'should preserve transaction') - t.equal(trans.trace.root.children.length, 1, 'should not create any segments') - watcher.close() - }) - fs.writeFile(name, content + 'more', function (err) { - t.error(err, 'should not fail to write to file') - }) - }) - }, 10) -}) - -test('watch (dir)', function (t) { - t.plan(5) - - const name = path.join(tempDir, 'watch-dir') - const content = 'some-content' - const agent = setupAgent(t) - - setTimeout(function () { - helper.runInTransaction(agent, function (trans) { - const watcher = fs.watch(tempDir, function (ev, file) { - t.equal(ev, 'rename') - t.equal(file, 'watch-dir') - t.equal(agent.getTransaction(), trans, 'should preserve transaction') - t.equal(trans.trace.root.children.length, 1, 'should not create any segments') - watcher.close() - }) - fs.writeFile(name, content, function (err) { - t.error(err, 'should not fail to write to file') - }) - }) - }, 10) -}) - -test('watch emitter', function (t) { - t.plan(5) - - const name = path.join(tempDir, 'watch') - const content = 'some-content' - const agent = setupAgent(t) - - setTimeout(function () { - helper.runInTransaction(agent, function (trans) { - const watcher = fs.watch(tempDir) - - watcher.on('change', function (ev, file) { - t.equal(ev, 'rename', 'should have expected event') - t.equal(file, 'watch', 'should be for correct directory') - - const tx = agent.getTransaction() - const root = trans.trace.root - t.equal(tx && tx.id, trans.id, 'should preserve transaction') - t.equal(root.children.length, 1, 'should not create any segments') - - watcher.close() - }) - - fs.writeFile(name, content, function (err) { - t.error(err, 'should not fail to write to file') - }) - }) - }, 10) -}) - -test('watchFile', function (t) { - t.plan(5) - - const name = path.join(tempDir, 'watchFile') - const content = 'some-content' - const agent = setupAgent(t) - fs.writeFileSync(name, content) - - setTimeout(function () { - helper.runInTransaction(agent, function (trans) { - fs.watchFile(name, onChange) - - function onChange(cur, prev) { - t.ok(cur.mtime > prev.mtime, 'modified date incremented') - t.ok(cur.size > prev.size, 'content modified') - - t.equal(agent.getTransaction(), trans, 'should preserve transaction') - t.equal(trans.trace.root.children.length, 0, 'should not create any segments') - fs.unwatchFile(name, onChange) - } - }) - - fs.writeFile(name, content + 'more', function (err) { - t.error(err, 'should not fail to write to file') - }) - }, 10) -}) - -test('glob', { skip: isGlobSupported === false }, function (t) { - const name = path.join(tempDir, 'glob-me') - const content = 'some-content' - fs.writeFileSync(name, content) - const agent = setupAgent(t) - helper.runInTransaction(agent, function (tx) { - fs.glob(`${tempDir}${path.sep}*glob-me*`, function (error, matches) { - t.error(error) - - const match = matches.find((m) => m.includes('glob-me')) - t.ok(match, 'glob found file') - - verifySegments(t, agent, NAMES.FS.PREFIX + 'glob') - - tx.end() - t.ok(checkMetric(['glob'], agent, tx.name), 'metric should exist after transaction end') - }) - }) -}) - -function setupAgent(t) { - const agent = helper.instrumentMockedAgent() - t.teardown(function () { - helper.unloadAgent(agent) - }) - - return agent -} - -function getMetrics(agent) { - return agent.metrics._metrics -} diff --git a/test/integration/core/fs.test.js b/test/integration/core/fs.test.js new file mode 100644 index 0000000000..4e62c52a33 --- /dev/null +++ b/test/integration/core/fs.test.js @@ -0,0 +1,1006 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const { tspl } = require('@matteo.collina/tspl') +const path = require('path') +const fs = require('fs') +const os = require('os') +const helper = require('../../lib/agent_helper') +const verifySegments = require('./verify') +const NAMES = require('../../../lib/metrics/names') +const crypto = require('crypto') + +const isGlobSupported = require('semver').satisfies(process.version, '>=22.0.0') +const tempDir = path.join(os.tmpdir(), crypto.randomUUID()) +fs.mkdirSync(tempDir) +// Set umask before fs tests (for normalizing create mode on OS X and linux) +process.umask('0000') + +function checkMetric(names, agent, scope) { + let res = true + const agentMetrics = agent.metrics._metrics + const metrics = scope ? agentMetrics.scoped[scope] : agentMetrics.unscoped + names.forEach((name) => { + res = res && metrics[NAMES.FS.PREFIX + name] + }) + return res +} + +test.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.instrumentMockedAgent() +}) + +test.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) +}) + +test.after(() => { + fs.rmSync(tempDir, { recursive: true, force: true }) +}) + +test('rename', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 13 }) + const name = path.join(tempDir, 'rename-me') + const newName = path.join(tempDir, 'renamed') + const content = 'some-content' + fs.writeFileSync(name, content) + helper.runInTransaction(agent, function (trans) { + fs.rename(name, newName, function (err) { + plan.ok(!err, 'should not error') + plan.ok(fs.existsSync(newName), 'file with new name should exist') + plan.ok(!fs.existsSync(name), 'file with old name should not exist') + plan.equal( + fs.readFileSync(newName, 'utf8'), + content, + 'file with new name should have expected contents' + ) + verifySegments({ agent, name: NAMES.FS.PREFIX + 'rename', assert: plan }) + + trans.end() + plan.ok( + checkMetric(['rename'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + await plan.completed +}) + +test('truncate', async function (t) { + const plan = tspl(t, { plan: 12 }) + const { agent } = t.nr + const name = path.join(tempDir, 'truncate-me') + const content = 'some-content' + fs.writeFileSync(name, content) + helper.runInTransaction(agent, function (trans) { + // if fs.ftruncate isn't around, it means that fs.truncate uses a file descriptor + // rather than a path, and won't trigger an 'open' segment due to implementation + // differences. This is mostly just a version check for v0.8. + const expectedSegments = ['open', 'truncate'] + fs.truncate(name, 4, function (err) { + plan.ok(!err, 'should not error') + plan.equal(fs.readFileSync(name, 'utf8'), content.slice(0, 4), 'content should be truncated') + verifySegments({ + agent, + name: NAMES.FS.PREFIX + 'truncate', + children: [NAMES.FS.PREFIX + 'open'], + assert: plan + }) + + trans.end() + plan.ok( + checkMetric(expectedSegments, agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + await plan.completed +}) + +test('ftruncate', async function (t) { + const plan = tspl(t, { plan: 11 }) + const { agent } = t.nr + const name = path.join(tempDir, 'ftruncate-me') + const content = 'some-content' + fs.writeFileSync(name, content) + const fd = fs.openSync(name, 'r+') + helper.runInTransaction(agent, function (trans) { + fs.ftruncate(fd, 4, function (err) { + plan.ok(!err, 'should not error') + plan.equal(fs.readFileSync(name, 'utf8'), content.slice(0, 4), 'content should be truncated') + verifySegments({ agent, name: NAMES.FS.PREFIX + 'ftruncate', assert: plan }) + + trans.end() + plan.ok( + checkMetric(['ftruncate'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('chown', async function (t) { + const plan = tspl(t, { plan: 10 }) + const { agent } = t.nr + const name = path.join(tempDir, 'chown-me') + const content = 'some-content' + fs.writeFileSync(name, content) + const uid = 0 + const gid = 0 + helper.runInTransaction(agent, function (trans) { + fs.chown(name, uid, gid, function (err) { + plan.ok(err, 'should error for non root users') + verifySegments({ agent, name: NAMES.FS.PREFIX + 'chown', assert: plan }) + + trans.end() + plan.ok( + checkMetric(['chown'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + await plan.completed +}) + +test('fchown', async function (t) { + const plan = tspl(t, { plan: 10 }) + const { agent } = t.nr + const name = path.join(tempDir, 'chown-me') + const content = 'some-content' + fs.writeFileSync(name, content) + const fd = fs.openSync(name, 'r+') + const uid = 0 + const gid = 0 + helper.runInTransaction(agent, function (trans) { + fs.fchown(fd, uid, gid, function (err) { + plan.ok(err, 'should error for non root users') + verifySegments({ agent, name: NAMES.FS.PREFIX + 'fchown', assert: plan }) + + trans.end() + plan.ok( + checkMetric(['fchown'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +// Only exists on Darwin currently, using this check to catch if it +// appears in other versions too. +test('lchown', { skip: fs.lchown === undefined }, async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 10 }) + const name = path.join(tempDir, 'chown-me') + const content = 'some-content' + fs.writeFileSync(name, content) + const uid = 0 + const gid = 0 + helper.runInTransaction(agent, function (trans) { + fs.lchown(name, uid, gid, function (err) { + plan.ok(err, 'should error for non root users') + verifySegments({ agent, name: NAMES.FS.PREFIX + 'lchown', assert: plan }) + + trans.end() + const names = ['lchown'] + plan.ok(checkMetric(names, agent, trans.name), 'metric should exist after transaction end') + }) + }) + await plan.completed +}) + +test('chmod', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 12 }) + const name = path.join(tempDir, 'chmod-me') + const content = 'some-content' + fs.writeFileSync(name, content) + plan.equal((fs.statSync(name).mode & 0x1ff).toString(8), '666') + helper.runInTransaction(agent, function (trans) { + fs.chmod(name, '0777', function (err) { + plan.equal(err, null, 'should not error') + helper.unloadAgent(agent) + plan.equal((fs.statSync(name).mode & 0x1ff).toString(8), '777') + verifySegments({ agent, name: NAMES.FS.PREFIX + 'chmod', assert: plan }) + + trans.end() + plan.ok( + checkMetric(['chmod'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +// Only exists on Darwin currently, using this check to catch if it +// appears in other versions too. +// eslint-disable-next-line node/no-deprecated-api +test('lchmod', { skip: fs.lchmod === undefined }, async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 13 }) + const name = path.join(tempDir, 'lchmod-me') + const content = 'some-content' + fs.writeFileSync(name, content) + plan.equal((fs.statSync(name).mode & 0x1ff).toString(8), '666') + helper.runInTransaction(agent, function (trans) { + // eslint-disable-next-line node/no-deprecated-api + fs.lchmod(name, '0777', function (err) { + plan.equal(err, null, 'should not error') + plan.equal((fs.statSync(name).mode & 0x1ff).toString(8), '777') + verifySegments({ + agent, + name: NAMES.FS.PREFIX + 'lchmod', + children: [NAMES.FS.PREFIX + 'open'], + assert: plan + }) + + trans.end() + plan.ok( + checkMetric(['lchmod', 'open'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('fchmod', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 12 }) + const name = path.join(tempDir, 'fchmod-me') + const content = 'some-content' + fs.writeFileSync(name, content) + const fd = fs.openSync(name, 'r+') + plan.equal((fs.statSync(name).mode & 0x1ff).toString(8), '666') + helper.runInTransaction(agent, function (trans) { + fs.fchmod(fd, '0777', function (err) { + plan.equal(err, null, 'should not error') + plan.equal((fs.statSync(name).mode & 0x1ff).toString(8), '777') + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'fchmod' }) + + trans.end() + plan.ok( + checkMetric(['fchmod'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('stat', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 11 }) + const name = path.join(tempDir, 'stat-me') + const content = 'some-content' + fs.writeFileSync(name, content) + helper.runInTransaction(agent, function (trans) { + fs.stat(name, function (err, stat) { + plan.equal(err, null, 'should not error') + plan.equal((stat.mode & 0x1ff).toString(8), '666') + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'stat' }) + + trans.end() + plan.ok(checkMetric(['stat'], agent, trans.name), 'metric should exist after transaction end') + }) + }) + + await plan.completed +}) + +test('lstat', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 11 }) + const name = path.join(tempDir, 'lstat-me') + const content = 'some-content' + fs.writeFileSync(name, content) + helper.runInTransaction(agent, function (trans) { + fs.lstat(name, function (err, stat) { + plan.equal(err, null, 'should not error') + plan.equal((stat.mode & 0x1ff).toString(8), '666') + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'lstat' }) + + trans.end() + plan.ok( + checkMetric(['lstat'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('fstat', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 11 }) + const name = path.join(tempDir, 'fstat-me') + const content = 'some-content' + fs.writeFileSync(name, content) + const fd = fs.openSync(name, 'r+') + helper.runInTransaction(agent, function (trans) { + fs.fstat(fd, function (err, stat) { + plan.equal(err, null, 'should not error') + plan.equal((stat.mode & 0x1ff).toString(8), '666') + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'fstat' }) + + trans.end() + plan.ok( + checkMetric(['fstat'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('link', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 11 }) + const name = path.join(tempDir, 'link-to-me') + const link = path.join(tempDir, 'link-me') + const content = 'some-content' + fs.writeFileSync(name, content) + helper.runInTransaction(agent, function (trans) { + fs.link(name, link, function (err) { + plan.equal(err, null, 'should not error') + plan.equal(fs.statSync(name).ino, fs.statSync(link).ino, 'should point to the same file') + + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'link' }) + + trans.end() + plan.ok(checkMetric(['link'], agent, trans.name), 'metric should exist after transaction end') + }) + }) + + await plan.completed +}) + +test('symlink', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 11 }) + const name = path.join(tempDir, 'symlink-to-me') + const link = path.join(tempDir, 'symlink-me') + const content = 'some-content' + fs.writeFileSync(name, content) + helper.runInTransaction(agent, function (trans) { + fs.symlink(name, link, function (err) { + plan.equal(err, null, 'should not error') + plan.equal(fs.readlinkSync(link), name, 'should point to the same file') + + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'symlink' }) + + trans.end() + plan.ok( + checkMetric(['symlink'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('readlink', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 11 }) + const name = path.join(tempDir, 'readlink') + const link = path.join(tempDir, 'readlink-me') + const content = 'some-content' + fs.writeFileSync(name, content) + fs.symlinkSync(name, link) + helper.runInTransaction(agent, function (trans) { + fs.readlink(link, function (err, target) { + plan.equal(err, null, 'should not error') + plan.equal(target, name, 'should point to the same file') + + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'readlink' }) + + trans.end() + plan.ok( + checkMetric(['readlink'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + await plan.completed +}) + +test('realpath', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 12 }) + const name = path.join(tempDir, 'realpath') + const link = path.join(tempDir, 'link-to-realpath') + const content = 'some-content' + fs.writeFileSync(name, content) + fs.symlinkSync(name, link) + const real = fs.realpathSync(name) + helper.runInTransaction(agent, function (trans) { + fs.realpath(link, function (err, target) { + plan.equal(err, null, 'should not error') + plan.equal(target, real, 'should point to the same file') + + verifySegments({ + agent, + assert: plan, + name: NAMES.FS.PREFIX + 'realpath', + children: [NAMES.FS.PREFIX + 'lstat'], + end: afterVerify + }) + + function afterVerify() { + trans.end() + const expectedMetrics = ['realpath'] + plan.ok( + checkMetric(expectedMetrics, agent, trans.name), + 'metric should exist after transaction end' + ) + } + }) + }) + + await plan.completed +}) + +test('realpath.native', async (t) => { + const { agent } = t.nr + const plan = tspl(t, { plan: 11 }) + const name = path.join(tempDir, 'realpath-native') + const link = path.join(tempDir, 'link-to-realpath-native') + const content = 'some-content' + fs.writeFileSync(name, content) + fs.symlinkSync(name, link) + const real = fs.realpathSync(name) + helper.runInTransaction(agent, (trans) => { + fs.realpath.native(link, (err, target) => { + plan.equal(err, null, 'should not error') + plan.equal(target, real, 'should point to the same file') + + verifySegments({ + agent, + assert: plan, + name: NAMES.FS.PREFIX + 'realpath.native', + end: afterVerify + }) + + function afterVerify() { + trans.end() + const expectedMetrics = ['realpath.native'] + plan.ok( + checkMetric(expectedMetrics, agent, trans.name), + 'metric should exist after transaction end' + ) + } + }) + }) + + await plan.completed +}) + +test('unlink', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 11 }) + const name = path.join(tempDir, 'unlink-from-me') + const link = path.join(tempDir, 'unlink-me') + const content = 'some-content' + fs.writeFileSync(name, content) + fs.symlinkSync(name, link) + helper.runInTransaction(agent, function (trans) { + fs.unlink(link, function (err) { + plan.equal(err, null, 'should not error') + plan.ok(!fs.existsSync(link), 'link should not exist') + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'unlink' }) + + trans.end() + plan.ok( + checkMetric(['unlink'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('mkdir', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 12 }) + const name = path.join(tempDir, 'mkdir') + helper.runInTransaction(agent, function (trans) { + fs.mkdir(name, function (err) { + plan.equal(err, null, 'should not error') + plan.ok(fs.existsSync(name), 'dir should exist') + plan.ok(fs.readdirSync(name), 'dir should be readable') + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'mkdir' }) + + trans.end() + plan.ok( + checkMetric(['mkdir'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('rmdir', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 11 }) + const name = path.join(tempDir, 'rmdir') + fs.mkdirSync(name) + helper.runInTransaction(agent, function (trans) { + fs.rmdir(name, function (err) { + plan.equal(err, null, 'should not error') + plan.ok(!fs.existsSync(name), 'dir should not exist') + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'rmdir' }) + + trans.end() + plan.ok( + checkMetric(['rmdir'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('readdir', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 11 }) + const name = path.join(tempDir, 'readdir') + fs.mkdirSync(name) + helper.runInTransaction(agent, function (trans) { + fs.readdir(name, function (err, data) { + plan.equal(err, null, 'should not error') + plan.deepEqual(data, [], 'should get list of contents') + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'readdir' }) + + trans.end() + plan.ok( + checkMetric(['readdir'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('close', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 10 }) + const name = path.join(tempDir, 'close-me') + const content = 'some-content' + fs.writeFileSync(name, content) + const fd = fs.openSync(name, 'r+') + helper.runInTransaction(agent, function (trans) { + fs.close(fd, function (err) { + plan.equal(err, null, 'should not error') + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'close' }) + + trans.end() + plan.ok( + checkMetric(['close'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('open', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 11 }) + const name = path.join(tempDir, 'open-me') + const content = 'some-content' + fs.writeFileSync(name, content) + helper.runInTransaction(agent, function (trans) { + fs.open(name, 'r+', function (err, fd) { + plan.equal(err, null, 'should not error') + plan.ok(fd, 'should get a file descriptor') + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'open' }) + + trans.end() + plan.ok(checkMetric(['open'], agent, trans.name), 'metric should exist after transaction end') + }) + }) + + await plan.completed +}) + +test('utimes', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 12 }) + const name = path.join(tempDir, 'utimes-me') + const content = 'some-content' + fs.writeFileSync(name, content) + const accessed = new Date(5) + const modified = new Date(15) + + helper.runInTransaction(agent, function (trans) { + fs.utimes(name, accessed, modified, function (err) { + plan.ok(!err, 'should not error') + const stats = fs.statSync(name) + plan.equal(stats.atime.toISOString(), accessed.toISOString()) + plan.equal(stats.mtime.toISOString(), modified.toISOString()) + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'utimes' }) + + trans.end() + plan.ok( + checkMetric(['utimes'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('futimes', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 12 }) + const name = path.join(tempDir, 'futimes-me') + const content = 'some-content' + fs.writeFileSync(name, content) + const fd = fs.openSync(name, 'r+') + const accessed = new Date(5) + const modified = new Date(15) + + helper.runInTransaction(agent, function (trans) { + fs.futimes(fd, accessed, modified, function (err) { + plan.ok(!err, 'should not error') + const stats = fs.statSync(name) + plan.equal(stats.atime.toISOString(), accessed.toISOString()) + plan.equal(stats.mtime.toISOString(), modified.toISOString()) + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'futimes' }) + + trans.end() + plan.ok( + checkMetric(['futimes'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('fsync', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 10 }) + const name = path.join(tempDir, 'fsync-me') + const content = 'some-content' + fs.writeFileSync(name, content) + const fd = fs.openSync(name, 'r+') + + helper.runInTransaction(agent, function (trans) { + fs.fsync(fd, function (err) { + plan.ok(!err, 'should not error') + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'fsync' }) + + trans.end() + plan.ok( + checkMetric(['fsync'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('readFile', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 11 }) + const name = path.join(tempDir, 'readFile') + const content = 'some-content' + fs.writeFileSync(name, content) + + helper.runInTransaction(agent, function (trans) { + fs.readFile(name, function (err, data) { + plan.ok(!err, 'should not error') + plan.equal(data.toString('utf8'), content) + + verifySegments({ + agent, + assert: plan, + name: NAMES.FS.PREFIX + 'readFile' + }) + + trans.end() + plan.ok( + checkMetric(['readFile'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('writeFile', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 12 }) + const name = path.join(tempDir, 'writeFile') + const content = 'some-content' + + helper.runInTransaction(agent, function (trans) { + fs.writeFile(name, content, function (err) { + plan.ok(!err, 'should not error') + plan.equal(fs.readFileSync(name).toString('utf8'), content) + verifySegments({ + agent, + assert: plan, + name: NAMES.FS.PREFIX + 'writeFile', + children: [NAMES.FS.PREFIX + 'open'] + }) + + trans.end() + plan.ok( + checkMetric(['writeFile', 'open'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('appendFile', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 12 }) + const name = path.join(tempDir, 'appendFile') + const content = 'some-content' + fs.writeFileSync(name, content) + + const expectedSegments = ['appendFile', 'writeFile'] + + helper.runInTransaction(agent, function (trans) { + fs.appendFile(name, '123', function (err) { + plan.ok(!err, 'should not error') + plan.equal(fs.readFileSync(name).toString('utf-8'), content + '123') + verifySegments({ + agent, + assert: plan, + name: NAMES.FS.PREFIX + 'appendFile', + children: [NAMES.FS.PREFIX + 'writeFile'] + }) + + trans.end() + plan.ok( + checkMetric(expectedSegments, agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('exists', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 10 }) + const name = path.join(tempDir, 'exists') + const content = 'some-content' + fs.writeFileSync(name, content) + + helper.runInTransaction(agent, function (trans) { + // eslint-disable-next-line node/no-deprecated-api + fs.exists(name, function (exists) { + plan.ok(exists, 'should exist') + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'exists' }) + + trans.end() + plan.ok( + checkMetric(['exists'], agent, trans.name), + 'metric should exist after transaction end' + ) + }) + }) + + await plan.completed +}) + +test('read', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 5 }) + const name = path.join(tempDir, 'read') + const content = 'some-content' + fs.writeFileSync(name, content) + const fd = fs.openSync(name, 'r+') + + const buf = Buffer.alloc(content.length) + + helper.runInTransaction(agent, function (trans) { + fs.read(fd, buf, 0, content.length, 0, function (err, len, data) { + plan.ok(!err, 'should not error') + plan.equal(len, 12, 'should read correct number of bytes') + plan.equal(data.toString('utf8'), content) + plan.equal(agent.getTransaction(), trans, 'should preserve transaction') + plan.equal(trans.trace.root.children.length, 0, 'should not create any segments') + }) + }) + + await plan.completed +}) + +test('write', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 5 }) + const name = path.join(tempDir, 'write') + const content = 'some-content' + fs.writeFileSync(name, '') + const fd = fs.openSync(name, 'r+') + + const buf = Buffer.from(content) + + helper.runInTransaction(agent, function (trans) { + fs.write(fd, buf, 0, content.length, 0, function (err, len) { + plan.ok(!err, 'should not error') + plan.equal(len, 12, 'should write correct number of bytes') + plan.equal(fs.readFileSync(name, 'utf8'), content) + plan.equal(agent.getTransaction(), trans, 'should preserve transaction') + plan.equal(trans.trace.root.children.length, 0, 'should not create any segments') + }) + }) + + await plan.completed +}) + +test('watch (file)', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 5 }) + + const name = path.join(tempDir, 'watch-file') + const content = 'some-content' + + fs.writeFileSync(name, content) + + setTimeout(function () { + helper.runInTransaction(agent, function (trans) { + const watcher = fs.watch(name, function (ev, file) { + plan.equal(ev, 'change', 'should be expected event') + + plan.equal(file, 'watch-file', 'should have correct file name') + plan.equal(agent.getTransaction(), trans, 'should preserve transaction') + plan.equal(trans.trace.root.children.length, 1, 'should not create any segments') + watcher.close() + }) + fs.writeFile(name, content + 'more', function (err) { + plan.ok(!err, 'should not fail to write to file') + }) + }) + }, 10) + + await plan.completed +}) + +test('watch (dir)', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 5 }) + + const name = path.join(tempDir, 'watch-dir') + const content = 'some-content' + + setTimeout(function () { + helper.runInTransaction(agent, function (trans) { + const watcher = fs.watch(tempDir, function (ev, file) { + plan.equal(ev, 'rename') + plan.equal(file, 'watch-dir') + plan.equal(agent.getTransaction(), trans, 'should preserve transaction') + plan.equal(trans.trace.root.children.length, 1, 'should not create any segments') + watcher.close() + }) + fs.writeFile(name, content, function (err) { + plan.ok(!err, 'should not fail to write to file') + }) + }) + }, 10) + + await plan.completed +}) + +test('watch emitter', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 5 }) + + const name = path.join(tempDir, 'watch') + const content = 'some-content' + + setTimeout(function () { + helper.runInTransaction(agent, function (trans) { + const watcher = fs.watch(tempDir) + + watcher.on('change', function (ev, file) { + plan.equal(ev, 'rename', 'should have expected event') + plan.equal(file, 'watch', 'should be for correct directory') + + const tx = agent.getTransaction() + const root = trans.trace.root + plan.equal(tx && tx.id, trans.id, 'should preserve transaction') + plan.equal(root.children.length, 1, 'should not create any segments') + + watcher.close() + }) + + fs.writeFile(name, content, function (err) { + plan.ok(!err, 'should not fail to write to file') + }) + }) + }, 10) + + await plan.completed +}) + +test('watchFile', async function (t) { + const { agent } = t.nr + const plan = tspl(t, { plan: 5 }) + + const name = path.join(tempDir, 'watchFile') + const content = 'some-content' + + fs.writeFileSync(name, content) + + setTimeout(function () { + helper.runInTransaction(agent, function (trans) { + fs.watchFile(name, onChange) + + function onChange(cur, prev) { + plan.ok(cur.mtime > prev.mtime, 'modified date incremented') + plan.ok(cur.size > prev.size, 'content modified') + + plan.equal(agent.getTransaction(), trans, 'should preserve transaction') + plan.equal(trans.trace.root.children.length, 0, 'should not create any segments') + fs.unwatchFile(name, onChange) + } + }) + + fs.writeFile(name, content + 'more', function (err) { + plan.ok(!err, 'should not fail to write to file') + }) + }, 10) + + await plan.completed +}) + +test('glob', { skip: isGlobSupported === false }, async function (t) { + const { agent } = t.nr + const name = path.join(tempDir, 'glob-me') + const plan = tspl(t, { plan: 11 }) + const content = 'some-content' + fs.writeFileSync(name, content) + + helper.runInTransaction(agent, function (tx) { + fs.glob(`${tempDir}${path.sep}*glob-me*`, function (error, matches) { + plan.ok(!error) + + const match = matches.find((m) => m.includes('glob-me')) + plan.ok(match, 'glob found file') + + verifySegments({ agent, assert: plan, name: NAMES.FS.PREFIX + 'glob' }) + + tx.end() + plan.ok(checkMetric(['glob'], agent, tx.name), 'metric should exist after transaction end') + }) + }) + + await plan.completed +}) diff --git a/test/integration/core/inspector.tap.js b/test/integration/core/inspector.test.js similarity index 69% rename from test/integration/core/inspector.tap.js rename to test/integration/core/inspector.test.js index 715ab6aa8f..802b5e9b2a 100644 --- a/test/integration/core/inspector.tap.js +++ b/test/integration/core/inspector.test.js @@ -5,30 +5,26 @@ 'use strict' -const test = require('tap').test +const test = require('node:test') +const assert = require('node:assert') // eslint-disable-next-line node/no-unsupported-features/node-builtins const inspector = require('inspector') const helper = require('../../lib/agent_helper') -test('inspector', function (t) { - const agent = setupAgent(t) +test('inspector', function (t, end) { + const agent = helper.instrumentMockedAgent() + t.after(() => { + helper.unloadAgent(agent) + }) + helper.runInTransaction(agent, function (txn) { const session = new inspector.Session() session.connect() session.post('Runtime.evaluate', { expression: '2 + 2' }, function () { const transaction = agent.getTransaction() - t.ok(transaction, 'should preserve transaction state') - t.equal(transaction.id, txn.id) - t.end() + assert.ok(transaction, 'should preserve transaction state') + assert.equal(transaction.id, txn.id) + end() }) }) }) - -function setupAgent(t) { - const agent = helper.instrumentMockedAgent() - t.teardown(function () { - helper.unloadAgent(agent) - }) - - return agent -} diff --git a/test/integration/core/native-promises/native-promises.tap.js b/test/integration/core/native-promises.test.js similarity index 60% rename from test/integration/core/native-promises/native-promises.tap.js rename to test/integration/core/native-promises.test.js index c7cac141dc..75a2f5f2bd 100644 --- a/test/integration/core/native-promises/native-promises.tap.js +++ b/test/integration/core/native-promises.test.js @@ -5,189 +5,59 @@ 'use strict' -const { test } = require('tap') - -const helper = require('../../../lib/agent_helper') -const asyncHooks = require('async_hooks') - -test('AsyncLocalStorage based tracking', (t) => { - t.autoend() - - const config = {} - - createPromiseTests(t, config) - - // Negative assertion case mirroring test for async-hooks. - // This is a new limitation due to AsyncLocalStorage propagation only on init. - // The timer-hop without context prior to .then() continuation causes the state loss. - t.test('DOES NOT maintain transaction context over contextless timer', (t) => { - const { agent } = setupAgent(t, config) - const tasks = [] - const intervalId = setInterval(() => { - while (tasks.length) { - tasks.pop()() - } - }, 10) - - t.teardown(() => { - clearInterval(intervalId) - }) - - helper.runInTransaction(agent, function (txn) { - t.ok(txn, 'transaction should not be null') - - const segment = txn.trace.root - agent.tracer.bindFunction(one, segment)() - - const wrapperTwo = agent.tracer.bindFunction(function () { - return two() - }, segment) - const wrapperThree = agent.tracer.bindFunction(function () { - return three() - }, segment) - - function one() { - return new Promise(executor).then(() => { - const tx = agent.getTransaction() - t.equal(tx ? tx.id : null, txn.id) - t.end() - }) - } - - function executor(resolve) { - tasks.push(() => { - next().then(() => { - const tx = agent.getTransaction() - t.notOk( - tx, - 'If fails, we have fixed a limitation and should check equal transaction IDs' - ) - resolve() - }) - }) - } - - function next() { - return Promise.resolve(wrapperTwo()) - } - - function two() { - return nextTwo() - } - - function nextTwo() { - return Promise.resolve(wrapperThree()) - } - - function three() {} - }) - }) - - // Negative assertion case mirroring test for async-hooks. - // This is a new limitation due to AsyncLocalStorage propagation only on init. - // The timer-hop without context prior to .then() continuation causes the state loss. - t.test('parent promises DO NOT persist perspective to problematic progeny', (t) => { - const { agent } = setupAgent(t, config) - const tasks = [] - const intervalId = setInterval(() => { - while (tasks.length) { - tasks.pop()() - } - }, 10) - - t.teardown(() => { - clearInterval(intervalId) - }) - - helper.runInTransaction(agent, function (txn) { - t.ok(txn, 'transaction should not be null') - - const p = Promise.resolve() - - tasks.push(() => { - p.then(() => { - const tx = agent.getTransaction() - - t.notOk(tx, 'If fails, we have fixed a limitation and should check equal transaction IDs') - t.end() - }) - }) - }) +const test = require('node:test') +const assert = require('node:assert') +const helper = require('../../lib/agent_helper') +const { tspl } = require('@matteo.collina/tspl') +const { createHook, checkCallMetrics, TestResource } = require('./promise-utils') + +test('AsyncLocalStorage based tracking', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.instrumentMockedAgent() + ctx.nr.tracer = helper.getTracer() }) - // Negative assertion case mirroring test for async-hooks. - // This is a new limitation due to AsyncLocalStorage propagation only on init. - // The timer-hop without context prior to .then() continuation causes the state loss. - t.test('unresolved parent promises DO NOT persist to problematic progeny', (t) => { - const { agent } = setupAgent(t, config) - const tasks = [] - const intervalId = setInterval(() => { - while (tasks.length) { - tasks.pop()() - } - }, 10) - - t.teardown(() => { - clearInterval(intervalId) - }) - - helper.runInTransaction(agent, function (txn) { - t.ok(txn, 'transaction should not be null') - - let parentResolve = null - const p = new Promise((resolve) => { - parentResolve = resolve - }) - - tasks.push(() => { - p.then(() => { - const tx = agent.getTransaction() - t.notOk(tx, 'If fails, we have fixed a limitation and should check equal transaction IDs') - - t.end() - }) - - // resolve parent after continuation scheduled - parentResolve() - }) - }) + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) -}) -function createPromiseTests(t, config) { - t.test('maintains context across await', function (t) { - const { agent } = setupAgent(t, config) - helper.runInTransaction(agent, async function (txn) { + await t.test('maintains context across await', async function (t) { + const { agent } = t.nr + await helper.runInTransaction(agent, async function (txn) { let transaction = agent.getTransaction() - t.equal(transaction && transaction.id, txn.id, 'should start in a transaction') + assert.equal(transaction && transaction.id, txn.id, 'should start in a transaction') await Promise.resolve("i'll be back") transaction = agent.getTransaction() - t.equal( + assert.equal( transaction && transaction.id, txn.id, 'should resume in the same transaction after await' ) txn.end() - t.end() }) }) - t.test('maintains context across multiple awaits', async (t) => { - const { agent } = setupAgent(t, config) + await t.test('maintains context across multiple awaits', async (t) => { + const { agent } = t.nr await helper.runInTransaction(agent, async function (createdTransaction) { let transaction = agent.getTransaction() - t.equal(transaction && transaction.id, createdTransaction.id, 'should start in a transaction') + assert.equal( + transaction && transaction.id, + createdTransaction.id, + 'should start in a transaction' + ) await firstFunction() transaction = agent.getTransaction() - t.equal(transaction && transaction.id, createdTransaction.id) + assert.equal(transaction && transaction.id, createdTransaction.id) await secondFunction() transaction = agent.getTransaction() - t.equal(transaction && transaction.id, createdTransaction.id) + assert.equal(transaction && transaction.id, createdTransaction.id) createdTransaction.end() @@ -195,13 +65,13 @@ function createPromiseTests(t, config) { await childFunction() transaction = agent.getTransaction() - t.equal(transaction && transaction.id, createdTransaction.id) + assert.equal(transaction && transaction.id, createdTransaction.id) } async function childFunction() { await new Promise((resolve) => { transaction = agent.getTransaction() - t.equal(transaction && transaction.id, createdTransaction.id) + assert.equal(transaction && transaction.id, createdTransaction.id) setTimeout(resolve, 1) }) @@ -215,22 +85,25 @@ function createPromiseTests(t, config) { }) }) - t.test('maintains context across promise chain', (t) => { - const { agent } = setupAgent(t, config) - helper.runInTransaction(agent, function (createdTransaction) { + await t.test('maintains context across promise chain', async (t) => { + const { agent } = t.nr + await helper.runInTransaction(agent, function (createdTransaction) { let transaction = agent.getTransaction() - t.equal(transaction && transaction.id, createdTransaction.id, 'should start in a transaction') - firstFunction() + assert.equal( + transaction && transaction.id, + createdTransaction.id, + 'should start in a transaction' + ) + return firstFunction() .then(() => { transaction = agent.getTransaction() - t.equal(transaction && transaction.id, createdTransaction.id) + assert.equal(transaction && transaction.id, createdTransaction.id) return secondFunction() }) .then(() => { transaction = agent.getTransaction() - t.equal(transaction && transaction.id, createdTransaction.id) + assert.equal(transaction && transaction.id, createdTransaction.id) createdTransaction.end() - t.end() }) function firstFunction() { @@ -240,7 +113,7 @@ function createPromiseTests(t, config) { function childFunction() { return new Promise((resolve) => { transaction = agent.getTransaction() - t.equal(transaction && transaction.id, createdTransaction.id) + assert.equal(transaction && transaction.id, createdTransaction.id) setTimeout(resolve, 1) }) @@ -254,38 +127,43 @@ function createPromiseTests(t, config) { }) }) - t.test('does not crash on multiple resolve calls', function (t) { - const { agent } = setupAgent(t, config) - helper.runInTransaction(agent, function () { - t.doesNotThrow(function () { - new Promise(function (res) { + await t.test('does not crash on multiple resolve calls', async function (t) { + const { agent } = t.nr + await helper.runInTransaction(agent, function () { + let promise = null + assert.doesNotThrow(function () { + promise = new Promise(function (res) { res() res() - }).then(t.end) + }) }) + return promise }) }) - t.test('restores context in inactive transactions', function (t) { - const { agent, tracer } = setupAgent(t, config) + await t.test('restores context in inactive transactions', async function (t) { + const plan = tspl(t, { plan: 1 }) + const { agent, tracer } = t.nr helper.runInTransaction(agent, function (txn) { const res = new TestResource(1) const root = tracer.getSegment() txn.end() res.doStuff(function () { - t.equal( + plan.equal( tracer.getSegment(), root, 'should restore a segment when its transaction has been ended' ) - t.end() }) }) + + await plan.completed }) - t.test('handles multi-entry callbacks correctly', function (t) { - const { agent, tracer } = setupAgent(t, config) + await t.test('handles multi-entry callbacks correctly', async function (t) { + const plan = tspl(t, { plan: 5 }) + const { agent, tracer } = t.nr helper.runInTransaction(agent, function () { const root = tracer.getSegment() @@ -302,50 +180,54 @@ function createPromiseTests(t, config) { tracer.setSegment(root) resA.doStuff(() => { - t.equal( + plan.equal( tracer.getSegment().name, aSeg.name, 'runInAsyncScope should restore the segment active when a resource was made' ) resB.doStuff(() => { - t.equal( + plan.equal( tracer.getSegment().name, bSeg.name, 'runInAsyncScope should restore the segment active when a resource was made' ) - - t.end() }) - t.equal( + plan.equal( tracer.getSegment().name, aSeg.name, 'runInAsyncScope should restore the segment active when a callback was called' ) }) - t.equal(tracer.getSegment().name, root.name, 'root should be restored after we are finished') + plan.equal( + tracer.getSegment().name, + root.name, + 'root should be restored after we are finished' + ) resA.doStuff(() => { - t.equal( + plan.equal( tracer.getSegment().name, aSeg.name, 'runInAsyncScope should restore the segment active when a resource was made' ) }) }) + + await plan.completed }) - t.test('maintains transaction context over setImmediate in-context', (t) => { - const { agent } = setupAgent(t, config) + await t.test('maintains transaction context over setImmediate in-context', async (t) => { + const plan = tspl(t, { plan: 3 }) + const { agent } = t.nr helper.runInTransaction(agent, function (txn) { - t.ok(txn, 'transaction should not be null') + plan.ok(txn, 'transaction should not be null') const segment = txn.trace.root agent.tracer.bindFunction(function one() { return new Promise(executor).then(() => { const tx = agent.getTransaction() - t.equal(tx ? tx.id : null, txn.id) - t.end() + plan.equal(tx ? tx.id : null, txn.id) }) }, segment)() @@ -360,7 +242,7 @@ function createPromiseTests(t, config) { setImmediate(() => { next().then(() => { const tx = agent.getTransaction() - t.equal(tx ? tx.id : null, txn.id) + plan.equal(tx ? tx.id : null, txn.id) resolve() }) }) @@ -380,20 +262,22 @@ function createPromiseTests(t, config) { function three() {} }) + + await plan.completed }) - t.test('maintains transaction context over process.nextTick in-context', (t) => { - const { agent } = setupAgent(t, config) + await t.test('maintains transaction context over process.nextTick in-context', async (t) => { + const plan = tspl(t, { plan: 3 }) + const { agent } = t.nr helper.runInTransaction(agent, function (txn) { - t.ok(txn, 'transaction should not be null') + plan.ok(txn, 'transaction should not be null') const segment = txn.trace.root agent.tracer.bindFunction(function one() { return new Promise(executor).then(() => { const tx = agent.getTransaction() - t.equal(tx ? tx.id : null, txn.id) - t.end() + plan.equal(tx ? tx.id : null, txn.id) }) }, segment)() @@ -408,7 +292,7 @@ function createPromiseTests(t, config) { process.nextTick(() => { next().then(() => { const tx = agent.getTransaction() - t.equal(tx ? tx.id : null, txn.id) + plan.equal(tx ? tx.id : null, txn.id) resolve() }) }) @@ -428,20 +312,22 @@ function createPromiseTests(t, config) { function three() {} }) + + await plan.completed }) - t.test('maintains transaction context over setTimeout in-context', (t) => { - const { agent } = setupAgent(t, config) + await t.test('maintains transaction context over setTimeout in-context', async (t) => { + const plan = tspl(t, { plan: 3 }) + const { agent } = t.nr helper.runInTransaction(agent, function (txn) { - t.ok(txn, 'transaction should not be null') + plan.ok(txn, 'transaction should not be null') const segment = txn.trace.root agent.tracer.bindFunction(function one() { return new Promise(executor).then(() => { const tx = agent.getTransaction() - t.equal(tx ? tx.id : null, txn.id) - t.end() + plan.equal(tx ? tx.id : null, txn.id) }) }, segment)() @@ -456,7 +342,7 @@ function createPromiseTests(t, config) { setTimeout(() => { next().then(() => { const tx = agent.getTransaction() - t.equal(tx ? tx.id : null, txn.id) + plan.equal(tx ? tx.id : null, txn.id) resolve() }) }, 1) @@ -476,20 +362,22 @@ function createPromiseTests(t, config) { function three() {} }) + + await plan.completed }) - t.test('maintains transaction context over setInterval in-context', (t) => { - const { agent } = setupAgent(t, config) + await t.test('maintains transaction context over setInterval in-context', async (t) => { + const plan = tspl(t, { plan: 3 }) + const { agent } = t.nr helper.runInTransaction(agent, function (txn) { - t.ok(txn, 'transaction should not be null') + plan.ok(txn, 'transaction should not be null') const segment = txn.trace.root agent.tracer.bindFunction(function one() { return new Promise(executor).then(() => { const tx = agent.getTransaction() - t.equal(tx ? tx.id : null, txn.id) - t.end() + plan.equal(tx ? tx.id : null, txn.id) }) }, segment)() @@ -506,7 +394,7 @@ function createPromiseTests(t, config) { next().then(() => { const tx = agent.getTransaction() - t.equal(tx ? tx.id : null, txn.id) + plan.equal(tx ? tx.id : null, txn.id) resolve() }) }, 1) @@ -526,107 +414,167 @@ function createPromiseTests(t, config) { function three() {} }) + + await plan.completed }) -} - -function checkCallMetrics(t, testMetrics) { - // Tap also creates promises, so these counts don't quite match the tests. - const TAP_COUNT = 1 - - t.equal(testMetrics.initCalled - TAP_COUNT, 2, 'two promises were created') - t.equal(testMetrics.beforeCalled, 1, 'before hook called for all async promises') - t.equal( - testMetrics.beforeCalled, - testMetrics.afterCalled, - 'before should be called as many times as after' - ) - if (global.gc) { - global.gc() - return setTimeout(function () { - t.equal( - testMetrics.initCalled - TAP_COUNT, - testMetrics.destroyCalled, - 'all promises created were destroyed' - ) - t.end() + // Negative assertion case mirroring test for async-hooks. + // This is a new limitation due to AsyncLocalStorage propagation only on init. + // The timer-hop without context prior to .then() continuation causes the state loss. + await t.test('DOES NOT maintain transaction context over contextless timer', async (t) => { + const plan = tspl(t, { plan: 3 }) + const { agent } = t.nr + const tasks = [] + const intervalId = setInterval(() => { + while (tasks.length) { + tasks.pop()() + } }, 10) - } - t.end() -} - -test('promise hooks', function (t) { - t.autoend() - const testMetrics = { - initCalled: 0, - beforeCalled: 0, - afterCalled: 0, - destroyCalled: 0 - } - - const promiseIds = {} - const hook = asyncHooks.createHook({ - init: function initHook(id, type) { - if (type === 'PROMISE') { - promiseIds[id] = true - testMetrics.initCalled++ + + t.after(() => { + clearInterval(intervalId) + }) + + helper.runInTransaction(agent, function (txn) { + plan.ok(txn, 'transaction should not be null') + + const segment = txn.trace.root + agent.tracer.bindFunction(one, segment)() + + const wrapperTwo = agent.tracer.bindFunction(function () { + return two() + }, segment) + const wrapperThree = agent.tracer.bindFunction(function () { + return three() + }, segment) + + function one() { + return new Promise(executor).then(() => { + const tx = agent.getTransaction() + plan.equal(tx ? tx.id : null, txn.id) + }) } - }, - before: function beforeHook(id) { - if (promiseIds[id]) { - testMetrics.beforeCalled++ + + function executor(resolve) { + tasks.push(() => { + next().then(() => { + const tx = agent.getTransaction() + plan.ok( + !tx, + 'If fails, we have fixed a limitation and should check equal transaction IDs' + ) + resolve() + }) + }) } - }, - after: function afterHook(id) { - if (promiseIds[id]) { - testMetrics.afterCalled++ + + function next() { + return Promise.resolve(wrapperTwo()) } - }, - destroy: function destHook(id) { - if (promiseIds[id]) { - testMetrics.destroyCalled++ + + function two() { + return nextTwo() } - } - }) - hook.enable() - t.test('are only called once during the lifetime of a promise', function (t) { - new Promise(function (res) { - setTimeout(res, 10) - }).then(function () { - setImmediate(checkCallMetrics, t, testMetrics) + function nextTwo() { + return Promise.resolve(wrapperThree()) + } + + function three() {} }) + + await plan.completed }) -}) -function setupAgent(t, config) { - const agent = helper.instrumentMockedAgent(config) - t.teardown(function () { - helper.unloadAgent(agent) + // Negative assertion case mirroring test for async-hooks. + // This is a new limitation due to AsyncLocalStorage propagation only on init. + // The timer-hop without context prior to .then() continuation causes the state loss. + await t.test('parent promises DO NOT persist perspective to problematic progeny', async (t) => { + const plan = tspl(t, { plan: 2 }) + const { agent } = t.nr + const tasks = [] + const intervalId = setInterval(() => { + while (tasks.length) { + tasks.pop()() + } + }, 10) + + t.after(() => { + clearInterval(intervalId) + }) + + helper.runInTransaction(agent, function (txn) { + plan.ok(txn, 'transaction should not be null') + + const p = Promise.resolve() + + tasks.push(() => { + p.then(() => { + const tx = agent.getTransaction() + + plan.ok( + !tx, + 'If fails, we have fixed a limitation and should check equal transaction IDs' + ) + }) + }) + }) + + await plan.completed }) - const tracer = helper.getTracer() - - return { - agent, - tracer - } -} - -class TestResource extends asyncHooks.AsyncResource { - constructor(id) { - super('PROMISE', id) - } - - doStuff(callback) { - process.nextTick(() => { - if (this.runInAsyncScope) { - this.runInAsyncScope(callback) - } else { - this.emitBefore() - callback() - this.emitAfter() + // Negative assertion case mirroring test for async-hooks. + // This is a new limitation due to AsyncLocalStorage propagation only on init. + // The timer-hop without context prior to .then() continuation causes the state loss. + await t.test('unresolved parent promises DO NOT persist to problematic progeny', async (t) => { + const plan = tspl(t, { plan: 2 }) + const { agent } = t.nr + const tasks = [] + const intervalId = setInterval(() => { + while (tasks.length) { + tasks.pop()() } + }, 10) + + t.after(() => { + clearInterval(intervalId) }) - } -} + + helper.runInTransaction(agent, function (txn) { + plan.ok(txn, 'transaction should not be null') + + let parentResolve = null + const p = new Promise((resolve) => { + parentResolve = resolve + }) + + tasks.push(() => { + p.then(() => { + const tx = agent.getTransaction() + plan.ok( + !tx, + 'If fails, we have fixed a limitation and should check equal transaction IDs' + ) + }) + + // resolve parent after continuation scheduled + parentResolve() + }) + }) + + await plan.completed + }) + + await t.test( + 'promise hooks are only called once during the lifetime of a promise', + async function (t) { + const plan = tspl(t, { plan: 3 }) + const testMetrics = createHook() + await new Promise(function (res) { + setTimeout(res, 10) + }) + setImmediate(checkCallMetrics, plan, testMetrics) + await plan.completed + } + ) +}) diff --git a/test/integration/core/net.tap.js b/test/integration/core/net.tap.js deleted file mode 100644 index 78237cdf01..0000000000 --- a/test/integration/core/net.tap.js +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const test = require('tap').test -const net = require('net') -const helper = require('../../lib/agent_helper') - -function id(tx) { - return tx && tx.id -} - -test('createServer', function createServerTest(t) { - const { agent, tracer } = setupAgent(t) - - helper.runInTransaction(agent, function transactionWrapper(transaction) { - const server = net.createServer(handler) - - server.listen(4123, function listening() { - // leave transaction - tracer.setSegment(null) - const socket = net.connect({ port: 4123 }) - socket.write('test123') - socket.end() - }) - - function handler(socket) { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - socket.end('test') - t.equal( - tracer.getSegment().name, - 'net.Server.onconnection', - 'child segment should have correct name' - ) - - socket.on('data', function onData(data) { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - t.equal(data.toString(), 'test123') - socket.end() - setTimeout(server.close.bind(server, onClose), 0) - }) - } - - function onClose() { - const root = agent.getTransaction().trace.root - t.equal(root.children.length, 1, 'should have a single child') - const child = root.children[0] - t.equal(child.name, 'net.Server.onconnection', 'child segment should have correct name') - t.ok(child.timer.touched, 'child should started and ended') - t.equal(child.children.length, 1, 'child should have a single child segment') - const timeout = child.children[0] - t.equal(timeout.name, 'timers.setTimeout', 'timeout segment should have correct name') - t.ok(timeout.timer.touched, 'timeout should started and ended') - t.equal(timeout.children.length, 1, 'timeout should have a single callback segment') - t.end() - } - }) -}) - -test('connect', function connectTest(t) { - const { agent } = setupAgent(t) - - const server = net.createServer(function connectionHandler(socket) { - socket.on('data', function onData(data) { - t.equal(data.toString(), 'some data') - socket.end('end data') - }) - }) - - t.teardown(function () { - server.close() - }) - - server.listen(4123, function listening() { - helper.runInTransaction(agent, transactionWrapper) - }) - - function transactionWrapper(transaction) { - let count = 0 - const socket = net.createConnection({ port: 4123 }) - socket.on('data', function onData(data) { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - t.equal(data.toString(), 'end data') - ++count - }) - socket.on('end', function onEnd() { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - t.equal(count, 1) - setTimeout(verify, 0) - }) - - socket.on('connect', function onConnet() { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - socket.write('some data') - socket.end() - }) - - function verify() { - if (!t.passing()) { - return t.end() - } - - const root = agent.getTransaction().trace.root - t.equal(root.children.length, 1, 'should have a single child') - let connectSegment = root.children[0] - t.equal( - connectSegment.name, - 'net.createConnection', - 'connect segment should have correct name' - ) - t.ok(connectSegment.timer.touched, 'connect should started and ended') - - // Depending on the version of Node there may be another connection segment - // floating in the trace. - if (connectSegment.children[0].name === 'net.Socket.connect') { - connectSegment = connectSegment.children[0] - } - - t.equal(connectSegment.children.length, 2, 'connect should have a two child segment') - const dnsSegment = connectSegment.children[0] - const timeoutSegment = connectSegment.children[1] - - t.equal(dnsSegment.name, 'dns.lookup', 'dns segment should have correct name') - t.ok(dnsSegment.timer.touched, 'dns segment should started and ended') - t.equal(dnsSegment.children.length, 1, 'dns should have a single callback segment') - t.equal(timeoutSegment.name, 'timers.setTimeout', 'timeout segment should have correct name') - t.ok(timeoutSegment.timer.touched, 'timeout should started and ended') - t.equal(timeoutSegment.children.length, 1, 'timeout should have a single callback segment') - t.end() - } - } -}) - -test('createServer and connect', function createServerTest(t) { - const { agent, tracer } = setupAgent(t) - - helper.runInTransaction(agent, function transactionWrapper(transaction) { - const server = net.createServer(handler) - - server.listen(4123, function listening() { - const socket = net.connect({ port: 4123 }) - socket.write('test123') - socket.end() - }) - - function handler(socket) { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - socket.end('test') - t.equal( - tracer.getSegment().name, - 'net.Server.onconnection', - 'child segment should have correct name' - ) - - socket.on('data', function onData(data) { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - t.equal(data.toString(), 'test123') - socket.end() - server.close(onClose) - }) - } - - function onClose() { - const root = agent.getTransaction().trace.root - t.equal(root.children.length, 2, 'should have 2 children') - let clientSegment = root.children[0] - t.equal(clientSegment.name, 'net.connect', 'server segment should have correct name') - t.ok(clientSegment.timer.touched, 'server should started and ended') - - // Depending on the version of Node there may be another connection segment - // floating in the trace. - if (clientSegment.children[0].name === 'net.Socket.connect') { - clientSegment = clientSegment.children[0] - } - - t.equal(clientSegment.children.length, 1, 'clientSegment should only have one child') - const dnsSegment = clientSegment.children[0] - if (dnsSegment) { - t.equal(dnsSegment.name, 'dns.lookup', 'dnsSegment is named properly') - } else { - t.fail('did not have children, prevent undefined property lookup') - } - - const serverSegment = root.children[1] - t.equal( - serverSegment.name, - 'net.Server.onconnection', - 'server segment should have correct name' - ) - t.ok(serverSegment.timer.touched, 'server should started and ended') - t.equal(serverSegment.children.length, 0, 'should not have any server segments') - t.end() - } - }) -}) - -function setupAgent(t) { - const agent = helper.instrumentMockedAgent() - const tracer = helper.getTracer() - - t.teardown(function tearDown() { - helper.unloadAgent(agent) - }) - - return { - agent, - tracer - } -} diff --git a/test/integration/core/net.test.js b/test/integration/core/net.test.js new file mode 100644 index 0000000000..1e38ce4dec --- /dev/null +++ b/test/integration/core/net.test.js @@ -0,0 +1,213 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const net = require('net') +const helper = require('../../lib/agent_helper') + +function id(tx) { + return tx && tx.id +} + +test.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.instrumentMockedAgent() + ctx.nr.tracer = helper.getTracer() +}) + +test.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) +}) + +test('createServer', function createServerTest(t, end) { + const { agent, tracer } = t.nr + + helper.runInTransaction(agent, function transactionWrapper(transaction) { + const server = net.createServer(handler) + + server.listen(4123, function listening() { + // leave transaction + tracer.setSegment(null) + const socket = net.connect({ port: 4123 }) + socket.write('test123') + socket.end() + }) + + function handler(socket) { + assert.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') + socket.end('test') + assert.equal( + tracer.getSegment().name, + 'net.Server.onconnection', + 'child segment should have correct name' + ) + + socket.on('data', function onData(data) { + assert.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') + assert.equal(data.toString(), 'test123') + socket.end() + setTimeout(server.close.bind(server, onClose), 0) + }) + } + + function onClose() { + const root = agent.getTransaction().trace.root + assert.equal(root.children.length, 1, 'should have a single child') + const child = root.children[0] + assert.equal(child.name, 'net.Server.onconnection', 'child segment should have correct name') + assert.ok(child.timer.touched, 'child should started and ended') + assert.equal(child.children.length, 1, 'child should have a single child segment') + const timeout = child.children[0] + assert.equal(timeout.name, 'timers.setTimeout', 'timeout segment should have correct name') + assert.ok(timeout.timer.touched, 'timeout should started and ended') + assert.equal(timeout.children.length, 1, 'timeout should have a single callback segment') + end() + } + }) +}) + +test('connect', function connectTest(t, end) { + const { agent } = t.nr + + const server = net.createServer(function connectionHandler(socket) { + socket.on('data', function onData(data) { + assert.equal(data.toString(), 'some data') + socket.end('end data') + }) + }) + + t.after(function () { + server.close() + }) + + server.listen(4123, function listening() { + helper.runInTransaction(agent, transactionWrapper) + }) + + function transactionWrapper(transaction) { + let count = 0 + const socket = net.createConnection({ port: 4123 }) + socket.on('data', function onData(data) { + assert.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') + assert.equal(data.toString(), 'end data') + ++count + }) + socket.on('end', function onEnd() { + assert.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') + assert.equal(count, 1) + setTimeout(verify, 0) + }) + + socket.on('connect', function onConnet() { + assert.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') + socket.write('some data') + socket.end() + }) + + function verify() { + const root = agent.getTransaction().trace.root + assert.equal(root.children.length, 1, 'should have a single child') + let connectSegment = root.children[0] + assert.equal( + connectSegment.name, + 'net.createConnection', + 'connect segment should have correct name' + ) + assert.ok(connectSegment.timer.touched, 'connect should started and ended') + + // Depending on the version of Node there may be another connection segment + // floating in the trace. + if (connectSegment.children[0].name === 'net.Socket.connect') { + connectSegment = connectSegment.children[0] + } + + assert.equal(connectSegment.children.length, 2, 'connect should have a two child segment') + const dnsSegment = connectSegment.children[0] + const timeoutSegment = connectSegment.children[1] + + assert.equal(dnsSegment.name, 'dns.lookup', 'dns segment should have correct name') + assert.ok(dnsSegment.timer.touched, 'dns segment should started and ended') + assert.equal(dnsSegment.children.length, 1, 'dns should have a single callback segment') + assert.equal( + timeoutSegment.name, + 'timers.setTimeout', + 'timeout segment should have correct name' + ) + assert.ok(timeoutSegment.timer.touched, 'timeout should started and ended') + assert.equal( + timeoutSegment.children.length, + 1, + 'timeout should have a single callback segment' + ) + end() + } + } +}) + +test('createServer and connect', function createServerTest(t, end) { + const { agent, tracer } = t.nr + + helper.runInTransaction(agent, function transactionWrapper(transaction) { + const server = net.createServer(handler) + + server.listen(4123, function listening() { + const socket = net.connect({ port: 4123 }) + socket.write('test123') + socket.end() + }) + + function handler(socket) { + assert.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') + socket.end('test') + assert.equal( + tracer.getSegment().name, + 'net.Server.onconnection', + 'child segment should have correct name' + ) + + socket.on('data', function onData(data) { + assert.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') + assert.equal(data.toString(), 'test123') + socket.end() + server.close(onClose) + }) + } + + function onClose() { + const root = agent.getTransaction().trace.root + assert.equal(root.children.length, 2, 'should have 2 children') + let clientSegment = root.children[0] + assert.equal(clientSegment.name, 'net.connect', 'server segment should have correct name') + assert.ok(clientSegment.timer.touched, 'server should started and ended') + + // Depending on the version of Node there may be another connection segment + // floating in the trace. + if (clientSegment.children[0].name === 'net.Socket.connect') { + clientSegment = clientSegment.children[0] + } + + assert.equal(clientSegment.children.length, 1, 'clientSegment should only have one child') + const dnsSegment = clientSegment.children[0] + if (dnsSegment) { + assert.equal(dnsSegment.name, 'dns.lookup', 'dnsSegment is named properly') + } else { + assert.ok(0, 'did not have children, prevent undefined property lookup') + } + + const serverSegment = root.children[1] + assert.equal( + serverSegment.name, + 'net.Server.onconnection', + 'server segment should have correct name' + ) + assert.ok(serverSegment.timer.touched, 'server should started and ended') + assert.equal(serverSegment.children.length, 0, 'should not have any server segments') + end() + } + }) +}) diff --git a/test/integration/core/promise-utils.js b/test/integration/core/promise-utils.js new file mode 100644 index 0000000000..96ede5e14f --- /dev/null +++ b/test/integration/core/promise-utils.js @@ -0,0 +1,85 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const asyncHooks = require('async_hooks') + +function createHook() { + const testMetrics = { + initCalled: 0, + beforeCalled: 0, + afterCalled: 0, + destroyCalled: 0 + } + + let calls = 0 + const promiseIds = {} + const hook = asyncHooks.createHook({ + init: function initHook(id, type) { + if (type === 'PROMISE') { + // There are a lot of promises being run around the test framework + // just get the first two as they are for the test + if (calls < 2) { + promiseIds[id] = true + testMetrics.initCalled++ + } + calls++ + } + }, + before: function beforeHook(id) { + if (promiseIds[id]) { + testMetrics.beforeCalled++ + } + }, + after: function afterHook(id) { + if (promiseIds[id]) { + testMetrics.afterCalled++ + } + }, + destroy: function destHook(id) { + if (promiseIds[id]) { + testMetrics.destroyCalled++ + } + } + }) + + hook.enable() + + return testMetrics +} + +function checkCallMetrics(plan, testMetrics) { + plan.ok(testMetrics.initCalled, 2, 'two promises were created') + plan.equal(testMetrics.beforeCalled, 1, 'before hook called for all async promises') + plan.equal( + testMetrics.beforeCalled, + testMetrics.afterCalled, + 'before should be called as many times as after' + ) +} + +class TestResource extends asyncHooks.AsyncResource { + constructor(id) { + super('PROMISE', id) + } + + doStuff(callback) { + process.nextTick(() => { + if (this.runInAsyncScope) { + this.runInAsyncScope(callback) + } else { + this.emitBefore() + callback() + this.emitAfter() + } + }) + } +} + +module.exports = { + createHook, + checkCallMetrics, + TestResource +} diff --git a/test/integration/core/timers.tap.js b/test/integration/core/timers.tap.js deleted file mode 100644 index ef2d92cb61..0000000000 --- a/test/integration/core/timers.tap.js +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const tap = require('tap') -const timers = require('timers') -const helper = require('../../lib/agent_helper') -const verifySegments = require('./verify') - -tap.test('setTimeout', function testSetTimeout(t) { - const { agent } = setupAgent(t) - helper.runInTransaction(agent, function transactionWrapper() { - timers.setTimeout(function anonymous() { - verifySegments(t, agent, 'timers.setTimeout') - }, 0) - }) -}) - -tap.test('setImmediate', function testSetImmediate(t) { - t.autoend() - - t.test('segments', function (t) { - t.plan(2) - const { agent } = setupAgent(t) - helper.runInTransaction(agent, function transactionWrapper(tx) { - timers.setImmediate(function anonymous() { - t.equal(agent.getTransaction().id, tx.id, 'should be in expected transaction') - t.notOk( - agent.getTransaction().trace.root.children.length, - 'should not have any segment for setImmediate' - ) - }) - }) - }) - - t.test('async transaction', function (t) { - t.plan(2) - const { agent } = setupAgent(t) - - helper.runInTransaction(agent, function (tx) { - timers.setImmediate(function () { - t.ok(agent.getTransaction(), 'should be in a transaction') - t.equal(agent.getTransaction().id, tx.id, 'should be in correct transaction') - }) - }) - }) - - t.test('overlapping transactions', function (t) { - t.plan(5) - const { agent } = setupAgent(t) - let firstTx = null - - helper.runInTransaction(agent, function (tx) { - firstTx = tx - check(tx) - }) - - timers.setImmediate(function () { - helper.runInTransaction(agent, function (tx) { - t.not(tx.id, firstTx.id, 'should not conflate transactions') - check(tx) - }) - }) - - function check(tx) { - timers.setImmediate(function () { - t.ok(agent.getTransaction(), 'should be in a transaction') - t.equal(agent.getTransaction().id, tx.id, 'should be in correct transaction') - }) - } - }) - - t.test('nested setImmediate calls', function (t) { - t.plan(4) - - const { agent } = setupAgent(t) - - t.notOk(agent.getTransaction(), 'should not start in a transaction') - helper.runInTransaction(agent, function () { - setImmediate(function () { - t.ok(agent.getTransaction(), 'should have transaction in first immediate') - setImmediate(function () { - t.ok(agent.getTransaction(), 'should have tx in second immediate') - setImmediate(function () { - t.ok(agent.getTransaction(), 'should have tx in third immediate') - }) - }) - }) - }) - }) - - t.test('should not propagate segments for ended transaction', (t) => { - const { agent } = setupAgent(t) - - t.notOk(agent.getTransaction(), 'should not start in a transaction') - helper.runInTransaction(agent, (transaction) => { - transaction.end() - - helper.runInSegment(agent, 'test-segment', () => { - const segment = agent.tracer.getSegment() - t.not(segment.name, 'test-segment') - t.equal(segment.children.length, 0, 'should not propagate segments when transaction ends') - setImmediate(() => { - const segment = agent.tracer.getSegment() - t.not(segment.name, 'test-segment') - t.equal(segment.children.length, 0, 'should not propagate segments when transaction ends') - t.end() - }) - }) - }) - }) -}) - -tap.test('setInterval', function testSetInterval(t) { - const { agent } = setupAgent(t) - helper.runInTransaction(agent, function transactionWrapper() { - const interval = timers.setInterval(() => { - clearInterval(interval) - verifySegments(t, agent, 'timers.setInterval') - }, 10) - }) -}) - -tap.test('global setTimeout', function testSetTimeout(t) { - const { agent } = setupAgent(t) - helper.runInTransaction(agent, function transactionWrapper() { - setTimeout(function anonymous() { - verifySegments(t, agent, 'timers.setTimeout') - }, 0) - }) -}) - -tap.test('global setImmediate', function testSetImmediate(t) { - const { agent } = setupAgent(t) - helper.runInTransaction(agent, function transactionWrapper(transaction) { - setImmediate(function anonymous() { - t.equal(agent.getTransaction(), transaction) - t.equal(agent.getTransaction().trace.root.children.length, 0) - t.end() - }) - }) -}) - -tap.test('global setInterval', function testSetInterval(t) { - const { agent } = setupAgent(t) - helper.runInTransaction(agent, function transactionWrapper() { - const interval = setInterval(() => { - clearInterval(interval) - verifySegments(t, agent, 'timers.setInterval') - }, 10) - }) -}) - -tap.test('nextTick', function testNextTick(t) { - const { agent } = setupAgent(t) - helper.runInTransaction(agent, function transactionWrapper(transaction) { - process.nextTick(function callback() { - t.equal(agent.getTransaction(), transaction) - t.equal(agent.getTransaction().trace.root.children.length, 0) - t.end() - }) - }) -}) - -tap.test('nextTick with extra args', function testNextTick(t) { - const original = process.nextTick - process.nextTick = multiArgNextTick - const { agent } = setupAgent(t) - helper.runInTransaction(agent, function transactionWrapper(transaction) { - process.nextTick( - function callback() { - t.equal(agent.getTransaction(), transaction) - t.equal(agent.getTransaction().trace.root.children.length, 0) - t.same([].slice.call(arguments), [1, 2, 3]) - process.nextTick = original - t.end() - }, - 1, - 2, - 3 - ) - }) - - function multiArgNextTick(fn) { - const args = [].slice.call(arguments, 1) - original(function callFn() { - fn.apply(this, args) - }) - } -}) - -tap.test('clearImmediate', (t) => { - const { agent } = setupAgent(t) - const timer = setImmediate(t.fail) - - clearImmediate(timer) - - helper.runInTransaction(agent, function transactionWrapper(transaction) { - process.nextTick(function callback() { - const timer2 = setImmediate(t.fail) - t.notOk(transaction.trace.root.children[0]) - clearImmediate(timer2) - setImmediate(t.end.bind(t)) - }) - }) -}) - -tap.test('clearTimeout should function outside of transaction context', (t) => { - setupAgent(t) - - const timer = setTimeout(t.fail) - - clearTimeout(timer) - - setImmediate(t.end) -}) - -tap.test('clearTimeout should ignore segment created for timer', (t) => { - const { agent } = setupAgent(t) - - helper.runInTransaction(agent, function transactionWrapper(transaction) { - process.nextTick(function callback() { - const timer = setTimeout(t.fail) - - const timerSegment = transaction.trace.root.children[0] - t.equal(timerSegment.name, 'timers.setTimeout') - t.equal(timerSegment.ignore, false) - - clearTimeout(timer) - t.equal(timerSegment.ignore, true) - - setTimeout(t.end) - }) - }) -}) - -tap.test('clearTimeout should not ignore parent segment when opaque', (t) => { - const expectedParentName = 'opaque segment' - - const { agent } = setupAgent(t) - - helper.runInTransaction(agent, function transactionWrapper(transaction) { - process.nextTick(function callback() { - helper.runInSegment(agent, expectedParentName, (segment) => { - segment.opaque = true - - const timer = setTimeout(t.fail) - - const parentSegment = transaction.trace.root.children[0] - t.equal(parentSegment.name, expectedParentName) - t.equal(parentSegment.ignore, false) - - clearTimeout(timer) - t.equal(parentSegment.ignore, false) - - setTimeout(t.end) - }) - }) - }) -}) - -tap.test('clearTimeout should not ignore parent segment when internal', (t) => { - const expectedParentName = 'internal segment' - - const { agent } = setupAgent(t) - - helper.runInTransaction(agent, function transactionWrapper(transaction) { - process.nextTick(function callback() { - helper.runInSegment(agent, expectedParentName, (segment) => { - segment.internal = true - - const timer = setTimeout(t.fail) - - const parentSegment = transaction.trace.root.children[0] - t.equal(parentSegment.name, expectedParentName) - t.equal(parentSegment.ignore, false) - - clearTimeout(timer) - t.equal(parentSegment.ignore, false) - - setTimeout(t.end) - }) - }) - }) -}) - -function setupAgent(t) { - const agent = helper.instrumentMockedAgent() - - t.teardown(function tearDown() { - helper.unloadAgent(agent) - }) - - return { agent } -} diff --git a/test/integration/core/timers.test.js b/test/integration/core/timers.test.js new file mode 100644 index 0000000000..205378cb20 --- /dev/null +++ b/test/integration/core/timers.test.js @@ -0,0 +1,312 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const { tspl } = require('@matteo.collina/tspl') +const timers = require('timers') +const helper = require('../../lib/agent_helper') +const verifySegments = require('./verify') + +test.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.instrumentMockedAgent() +}) + +test.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) +}) + +test('setTimeout', function testSetTimeout(t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function transactionWrapper() { + timers.setTimeout(function anonymous() { + verifySegments({ agent, end, name: 'timers.setTimeout' }) + }, 0) + }) +}) + +test('setImmediate: segments', async function (t) { + const plan = tspl(t, { plan: 2 }) + const { agent } = t.nr + helper.runInTransaction(agent, function transactionWrapper(tx) { + timers.setImmediate(function anonymous() { + plan.equal(agent.getTransaction().id, tx.id, 'should be in expected transaction') + plan.ok( + !agent.getTransaction().trace.root.children.length, + 'should not have any segment for setImmediate' + ) + }) + }) + + await plan.completed +}) + +test('setImmediate: async transaction', async function (t) { + const plan = tspl(t, { plan: 2 }) + const { agent } = t.nr + + helper.runInTransaction(agent, function (tx) { + timers.setImmediate(function () { + plan.ok(agent.getTransaction(), 'should be in a transaction') + plan.equal(agent.getTransaction().id, tx.id, 'should be in correct transaction') + }) + }) + + await plan.completed +}) + +test('setImmediate: overlapping transactions', async function (t) { + const plan = tspl(t, { plan: 5 }) + const { agent } = t.nr + let firstTx = null + + helper.runInTransaction(agent, function (tx) { + firstTx = tx + check(tx) + }) + + timers.setImmediate(function () { + helper.runInTransaction(agent, function (tx) { + plan.notEqual(tx.id, firstTx.id, 'should not conflate transactions') + check(tx) + }) + }) + + function check(tx) { + timers.setImmediate(function () { + plan.ok(agent.getTransaction(), 'should be in a transaction') + plan.equal(agent.getTransaction().id, tx.id, 'should be in correct transaction') + }) + } + await plan.completed +}) + +test('setImmediate: nested calls', async function (t) { + const plan = tspl(t, { plan: 4 }) + + const { agent } = t.nr + + plan.ok(!agent.getTransaction(), 'should not start in a transaction') + helper.runInTransaction(agent, function () { + setImmediate(function () { + plan.ok(agent.getTransaction(), 'should have transaction in first immediate') + setImmediate(function () { + plan.ok(agent.getTransaction(), 'should have tx in second immediate') + setImmediate(function () { + plan.ok(agent.getTransaction(), 'should have tx in third immediate') + }) + }) + }) + }) + + await plan.completed +}) + +test('setImmediate: should not propagate segments for ended transaction', async (t) => { + const plan = tspl(t, { plan: 5 }) + const { agent } = t.nr + + plan.ok(!agent.getTransaction(), 'should not start in a transaction') + helper.runInTransaction(agent, (transaction) => { + transaction.end() + + helper.runInSegment(agent, 'test-segment', () => { + const segment = agent.tracer.getSegment() + plan.notEqual(segment.name, 'test-segment') + plan.equal(segment.children.length, 0, 'should not propagate segments when transaction ends') + setImmediate(() => { + const segment = agent.tracer.getSegment() + plan.notEqual(segment.name, 'test-segment') + plan.equal( + segment.children.length, + 0, + 'should not propagate segments when transaction ends' + ) + }) + }) + }) + + await plan.completed +}) + +test('setInterval', function testSetInterval(t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function transactionWrapper() { + const interval = timers.setInterval(() => { + clearInterval(interval) + verifySegments({ agent, end, name: 'timers.setInterval' }) + }, 10) + }) +}) + +test('global setTimeout', function testSetTimeout(t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function transactionWrapper() { + setTimeout(function anonymous() { + verifySegments({ agent, end, name: 'timers.setTimeout' }) + }, 0) + }) +}) + +test('global setImmediate', function testSetImmediate(t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function transactionWrapper(transaction) { + setImmediate(function anonymous() { + assert.equal(agent.getTransaction(), transaction) + assert.equal(agent.getTransaction().trace.root.children.length, 0) + end() + }) + }) +}) + +test('global setInterval', function testSetInterval(t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function transactionWrapper() { + const interval = setInterval(() => { + clearInterval(interval) + verifySegments({ agent, end, name: 'timers.setInterval' }) + }, 10) + }) +}) + +test('nextTick', async function testNextTick(t) { + const plan = tspl(t, { plan: 2 }) + const { agent } = t.nr + helper.runInTransaction(agent, function transactionWrapper(transaction) { + process.nextTick(function callback() { + plan.equal(agent.getTransaction(), transaction) + plan.equal(agent.getTransaction().trace.root.children.length, 0) + }) + }) + + await plan.completed +}) + +test('nextTick with extra args', async function testNextTick(t) { + const plan = tspl(t, { plan: 3 }) + const original = process.nextTick + process.nextTick = multiArgNextTick + const { agent } = t.nr + helper.runInTransaction(agent, function transactionWrapper(transaction) { + process.nextTick( + function callback() { + plan.equal(agent.getTransaction(), transaction) + plan.equal(agent.getTransaction().trace.root.children.length, 0) + plan.deepEqual([].slice.call(arguments), [1, 2, 3]) + process.nextTick = original + }, + 1, + 2, + 3 + ) + }) + + function multiArgNextTick(fn) { + const args = [].slice.call(arguments, 1) + original(function callFn() { + fn.apply(this, args) + }) + } + + await plan.completed +}) + +test('clearImmediate', (t, end) => { + const { agent } = t.nr + const timer = setImmediate(assert.fail) + + clearImmediate(timer) + + helper.runInTransaction(agent, function transactionWrapper(transaction) { + process.nextTick(function callback() { + const timer2 = setImmediate(assert.fail) + assert.ok(!transaction.trace.root.children[0]) + clearImmediate(timer2) + setImmediate(end) + }) + }) +}) + +test('clearTimeout should function outside of transaction context', (t, end) => { + const timer = setTimeout(assert.fail) + + clearTimeout(timer) + + setImmediate(end) +}) + +test('clearTimeout should ignore segment created for timer', async (t) => { + const plan = tspl(t, { plan: 3 }) + const { agent } = t.nr + + helper.runInTransaction(agent, function transactionWrapper(transaction) { + process.nextTick(function callback() { + const timer = setTimeout(plan.fail) + + const timerSegment = transaction.trace.root.children[0] + plan.equal(timerSegment.name, 'timers.setTimeout') + plan.equal(timerSegment.ignore, false) + + clearTimeout(timer) + plan.equal(timerSegment.ignore, true) + }) + }) + + await plan.completed +}) + +test('clearTimeout should not ignore parent segment when opaque', async (t) => { + const plan = tspl(t, { plan: 3 }) + const expectedParentName = 'opaque segment' + + const { agent } = t.nr + + helper.runInTransaction(agent, function transactionWrapper(transaction) { + process.nextTick(function callback() { + helper.runInSegment(agent, expectedParentName, (segment) => { + segment.opaque = true + + const timer = setTimeout(plan.fail) + const parentSegment = transaction.trace.root.children[0] + plan.equal(parentSegment.name, expectedParentName) + plan.equal(parentSegment.ignore, false) + + clearTimeout(timer) + plan.equal(parentSegment.ignore, false) + }) + }) + }) + + await plan.completed +}) + +test('clearTimeout should not ignore parent segment when internal', async (t) => { + const plan = tspl(t, { plan: 3 }) + const expectedParentName = 'internal segment' + + const { agent } = t.nr + + helper.runInTransaction(agent, function transactionWrapper(transaction) { + process.nextTick(function callback() { + helper.runInSegment(agent, expectedParentName, (segment) => { + segment.internal = true + + const timer = setTimeout(plan.fail) + + const parentSegment = transaction.trace.root.children[0] + plan.equal(parentSegment.name, expectedParentName) + plan.equal(parentSegment.ignore, false) + + clearTimeout(timer) + plan.equal(parentSegment.ignore, false) + }) + }) + }) + + await plan.completed +}) diff --git a/test/integration/core/util.tap.js b/test/integration/core/util.tap.js deleted file mode 100644 index 690bf9bbca..0000000000 --- a/test/integration/core/util.tap.js +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const test = require('tap').test -const util = require('util') -const path = require('path') -const helper = require('../../lib/agent_helper') - -test('promisify', { skip: !util.promisify }, function (t) { - t.autoend() - t.test('should work on setTimeout', function (t) { - t.plan(2) - const agent = helper.instrumentMockedAgent() - t.teardown(function () { - helper.unloadAgent(agent) - }) - const asyncTimeout = util.promisify(setTimeout) - asyncTimeout(10, 'foobar') - .then((val) => { - t.equal(val, 'foobar', 'setTimeout parameter should flow') - t.ok(true, 'should evaluate properly') - t.end() - }) - .catch((ex) => { - t.error(ex) - }) - }) - t.test('should work on setImmediate', function (t) { - t.plan(2) - const agent = helper.instrumentMockedAgent() - t.teardown(function () { - helper.unloadAgent(agent) - }) - const asyncImmediate = util.promisify(setImmediate) - asyncImmediate('foobar') - .then((val) => { - t.equal(val, 'foobar', 'setImmediate parameter should flow') - t.pass('should evaluate properly') - t.end() - }) - .catch((ex) => { - t.error(ex) - }) - }) - t.test('should work on child_process.exec', function (t) { - t.plan(3) - const agent = helper.instrumentMockedAgent() - t.teardown(function () { - helper.unloadAgent(agent) - }) - const asyncExec = util.promisify(require('child_process').exec) - asyncExec('ls') - .then((result) => { - t.type(result, 'object', 'first argument should be object') - t.type(result.stdout, 'string', 'should have string stdout') - t.type(result.stderr, 'string', 'should have string stderr') - }) - .catch((ex) => { - t.error(ex) - }) - }) - t.test('should work on child_process.execFile', function (t) { - t.plan(3) - const agent = helper.instrumentMockedAgent() - t.teardown(function () { - helper.unloadAgent(agent) - }) - const asyncExec = util.promisify(require('child_process').execFile) - asyncExec(path.join(__dirname, 'exec-me.js')) - .then((result) => { - t.type(result, 'object', 'first argument should be object') - t.type(result.stdout, 'string', 'should have string stdout') - t.type(result.stderr, 'string', 'should have string stderr') - }) - .catch((ex) => { - t.error(ex) - }) - }) - - t.test('should work on fs.exists', function (t) { - t.plan(1) - - const agent = helper.instrumentMockedAgent() - t.teardown(function () { - helper.unloadAgent(agent) - }) - - // eslint-disable-next-line node/no-deprecated-api - const asyncExists = util.promisify(require('fs').exists) - - asyncExists(path.join(__dirname, 'exec-me.js')) - .then(() => { - t.ok(true, 'should find file') - t.end() - }) - .catch((ex) => { - t.error(ex) - }) - }) -}) diff --git a/test/integration/core/util.test.js b/test/integration/core/util.test.js new file mode 100644 index 0000000000..af8a9ed117 --- /dev/null +++ b/test/integration/core/util.test.js @@ -0,0 +1,55 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const util = require('util') +const path = require('path') +const helper = require('../../lib/agent_helper') + +test('promisify', async function (t) { + t.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.instrumentMockedAgent() + }) + + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + }) + await t.test('should work on setTimeout', async function () { + const asyncTimeout = util.promisify(setTimeout) + const val = await asyncTimeout(10, 'foobar') + assert.equal(val, 'foobar', 'setTimeout parameter should flow') + }) + await t.test('should work on setImmediate', async function () { + const asyncImmediate = util.promisify(setImmediate) + const val = await asyncImmediate('foobar') + assert.equal(val, 'foobar', 'setImmediate parameter should flow') + }) + await t.test('should work on child_process.exec', async function () { + const asyncExec = util.promisify(require('child_process').exec) + const result = await asyncExec('ls') + assert.ok(typeof result === 'object', 'first argument should be object') + assert.ok(typeof result.stdout === 'string', 'should have string stdout') + assert.ok(typeof result.stderr === 'string', 'should have string stderr') + }) + await t.test('should work on child_process.execFile', async function () { + const asyncExec = util.promisify(require('child_process').execFile) + const result = await asyncExec(path.join(__dirname, 'exec-me.js')) + assert.ok(typeof result === 'object', 'first argument should be object') + assert.ok(typeof result.stdout === 'string', 'should have string stdout') + assert.ok(typeof result.stderr === 'string', 'should have string stderr') + }) + + await t.test('should work on fs.exists', async function () { + // eslint-disable-next-line node/no-deprecated-api + const asyncExists = util.promisify(require('fs').exists) + + const result = await asyncExists(path.join(__dirname, 'exec-me.js')) + assert.equal(result, true, 'should find file') + }) +}) diff --git a/test/integration/core/verify.js b/test/integration/core/verify.js index c6f209ba04..f2f0a1ea0a 100644 --- a/test/integration/core/verify.js +++ b/test/integration/core/verify.js @@ -7,31 +7,32 @@ module.exports = verifySegments -function verifySegments(t, agent, name, extras, done) { +function verifySegments({ agent, name, children = [], end, assert = require('node:assert') }) { const root = agent.getTransaction().trace.root - if (!extras) { - extras = [] - } - t.equal(root.children.length, 1, 'should have a single child') + assert.equal(root.children.length, 1, 'should have a single child') const child = root.children[0] - t.equal(child.name, name, 'child segment should have correct name') - t.ok(child.timer.touched, 'child should started and ended') - t.equal(child.children.length, 1 + extras.length, 'child should have a single callback segment') + assert.equal(child.name, name, 'child segment should have correct name') + assert.ok(child.timer.touched, 'child should started and ended') + assert.equal( + child.children.length, + 1 + children.length, + 'child should have a single callback segment' + ) - for (let i = 0; i < extras.length; ++i) { - t.equal(child.children[i].name, extras[i]) + for (let i = 0; i < children.length; ++i) { + assert.equal(child.children[i].name, children[i]) } const callback = child.children[child.children.length - 1] - t.ok( + assert.ok( callback.name === 'Callback: anonymous' || callback.name === 'Callback: ', 'callback segment should have correct name' ) - t.ok(callback.timer.start, 'callback should have started') - t.notOk(callback.timer.touched, 'callback should not have ended') + assert.ok(callback.timer.start, 'callback should have started') + assert.ok(!callback.timer.touched, 'callback should not have ended') setTimeout(function () { - t.ok(callback.timer.touched, 'callback should have ended') - done ? done() : t.end() + assert.ok(callback.timer.touched, 'callback should have ended') + end?.() }, 0) } diff --git a/test/integration/core/zlib.tap.js b/test/integration/core/zlib.tap.js deleted file mode 100644 index d5e7240ed2..0000000000 --- a/test/integration/core/zlib.tap.js +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const test = require('tap').test -const zlib = require('zlib') -const helper = require('../../lib/agent_helper') -const verifySegments = require('./verify') -const concat = require('concat-stream') - -// Prepare our data values. Note that since the agent isn't loaded yet these -// compressions are immune to agent fiddling. -const CONTENT = 'some content' -const DEFLATED_CONTENT = zlib.deflateSync(CONTENT).toString('base64') -const DEFLATED_RAW = zlib.deflateRawSync(CONTENT).toString('base64') -const GZIP_CONTENT = zlib.gzipSync(CONTENT).toString('base64') - -test('deflate', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - zlib.deflate(CONTENT, function (err, data) { - t.notOk(err, 'should not error') - t.equal(data.toString('base64'), DEFLATED_CONTENT) - verifySegments(t, agent, 'zlib.deflate') - }) - }) -}) - -test('deflateRaw', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - zlib.deflateRaw(CONTENT, function (err, data) { - t.notOk(err, 'should not error') - t.equal(data.toString('base64'), DEFLATED_RAW) - verifySegments(t, agent, 'zlib.deflateRaw') - }) - }) -}) - -test('gzip', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - zlib.gzip(CONTENT, function (err, data) { - t.notOk(err, 'should not error') - t.equal(data.toString('base64'), GZIP_CONTENT) - verifySegments(t, agent, 'zlib.gzip') - }) - }) -}) - -test('inflate', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - zlib.inflate(Buffer.from(DEFLATED_CONTENT, 'base64'), function (err, data) { - t.notOk(err, 'should not error') - t.equal(data.toString(), CONTENT) - verifySegments(t, agent, 'zlib.inflate') - }) - }) -}) - -test('inflateRaw', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - zlib.inflateRaw(Buffer.from(DEFLATED_RAW, 'base64'), function (err, data) { - t.notOk(err, 'should not error') - t.equal(data.toString(), CONTENT) - verifySegments(t, agent, 'zlib.inflateRaw') - }) - }) -}) - -test('gunzip', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - zlib.gunzip(Buffer.from(GZIP_CONTENT, 'base64'), function (err, data) { - t.notOk(err, 'should not error') - t.equal(data.toString(), CONTENT) - verifySegments(t, agent, 'zlib.gunzip') - }) - }) -}) - -test('unzip', function (t) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function () { - zlib.unzip(Buffer.from(GZIP_CONTENT, 'base64'), function (err, data) { - t.notOk(err, 'should not error') - t.equal(data.toString(), CONTENT) - verifySegments(t, agent, 'zlib.unzip') - }) - }) -}) - -test('createGzip', function (t) { - testStream(t, 'createGzip', CONTENT, GZIP_CONTENT) -}) - -test('createGunzip', function (t) { - testStream( - t, - 'createGunzip', - Buffer.from(GZIP_CONTENT, 'base64'), - Buffer.from(CONTENT).toString('base64') - ) -}) - -test('createUnzip', function (t) { - testStream( - t, - 'createUnzip', - Buffer.from(GZIP_CONTENT, 'base64'), - Buffer.from(CONTENT).toString('base64') - ) -}) - -test('createDeflate', function (t) { - testStream(t, 'createDeflate', CONTENT, DEFLATED_CONTENT) -}) - -test('createInflate', function (t) { - testStream( - t, - 'createInflate', - Buffer.from(DEFLATED_CONTENT, 'base64'), - Buffer.from(CONTENT).toString('base64') - ) -}) - -test('createDeflateRaw', function (t) { - testStream(t, 'createDeflateRaw', CONTENT, DEFLATED_RAW) -}) - -test('createInflateRaw', function (t) { - testStream( - t, - 'createInflateRaw', - Buffer.from(DEFLATED_RAW, 'base64'), - Buffer.from(CONTENT).toString('base64') - ) -}) - -function testStream(t, method, src, out) { - const agent = setupAgent(t) - helper.runInTransaction(agent, function (transaction) { - const concatStream = concat(check) - - // The check callback is called when the stream finishes. - const stream = zlib[method]() - stream.pipe(concatStream) - stream.end(src) - - function check(result) { - t.equal(result.toString('base64'), out, 'should have correct result') - t.equal(agent.getTransaction(), transaction) - t.end() - } - }) -} - -function setupAgent(t) { - const agent = helper.instrumentMockedAgent() - t.teardown(function () { - helper.unloadAgent(agent) - }) - - return agent -} diff --git a/test/integration/core/zlib.test.js b/test/integration/core/zlib.test.js new file mode 100644 index 0000000000..c74c56a73e --- /dev/null +++ b/test/integration/core/zlib.test.js @@ -0,0 +1,182 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const zlib = require('zlib') +const helper = require('../../lib/agent_helper') +const verifySegments = require('./verify') +const concat = require('concat-stream') + +// Prepare our data values. Note that since the agent isn't loaded yet these +// compressions are immune to agent fiddling. +const CONTENT = 'some content' +const DEFLATED_CONTENT = zlib.deflateSync(CONTENT).toString('base64') +const DEFLATED_RAW = zlib.deflateRawSync(CONTENT).toString('base64') +const GZIP_CONTENT = zlib.gzipSync(CONTENT).toString('base64') + +test.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.instrumentMockedAgent() +}) + +test.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) +}) + +test('deflate', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + zlib.deflate(CONTENT, function (err, data) { + assert.ok(!err, 'should not error') + assert.equal(data.toString('base64'), DEFLATED_CONTENT) + verifySegments({ agent, end, name: 'zlib.deflate' }) + }) + }) +}) + +test('deflateRaw', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + zlib.deflateRaw(CONTENT, function (err, data) { + assert.ok(!err, 'should not error') + assert.equal(data.toString('base64'), DEFLATED_RAW) + verifySegments({ agent, end, name: 'zlib.deflateRaw' }) + }) + }) +}) + +test('gzip', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + zlib.gzip(CONTENT, function (err, data) { + assert.ok(!err, 'should not error') + assert.equal(data.toString('base64'), GZIP_CONTENT) + verifySegments({ agent, end, name: 'zlib.gzip' }) + }) + }) +}) + +test('inflate', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + zlib.inflate(Buffer.from(DEFLATED_CONTENT, 'base64'), function (err, data) { + assert.ok(!err, 'should not error') + assert.equal(data.toString(), CONTENT) + verifySegments({ agent, end, name: 'zlib.inflate' }) + }) + }) +}) + +test('inflateRaw', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + zlib.inflateRaw(Buffer.from(DEFLATED_RAW, 'base64'), function (err, data) { + assert.ok(!err, 'should not error') + assert.equal(data.toString(), CONTENT) + verifySegments({ agent, end, name: 'zlib.inflateRaw' }) + }) + }) +}) + +test('gunzip', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + zlib.gunzip(Buffer.from(GZIP_CONTENT, 'base64'), function (err, data) { + assert.ok(!err, 'should not error') + assert.equal(data.toString(), CONTENT) + verifySegments({ agent, end, name: 'zlib.gunzip' }) + }) + }) +}) + +test('unzip', function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function () { + zlib.unzip(Buffer.from(GZIP_CONTENT, 'base64'), function (err, data) { + assert.ok(!err, 'should not error') + assert.equal(data.toString(), CONTENT) + verifySegments({ agent, end, name: 'zlib.unzip' }) + }) + }) +}) + +test('createGzip', function (t, end) { + const { agent } = t.nr + testStream({ agent, end, method: 'createGzip', src: CONTENT, out: GZIP_CONTENT }) +}) + +test('createGunzip', function (t, end) { + const { agent } = t.nr + testStream({ + agent, + end, + method: 'createGunzip', + src: Buffer.from(GZIP_CONTENT, 'base64'), + out: Buffer.from(CONTENT).toString('base64') + }) +}) + +test('createUnzip', function (t, end) { + const { agent } = t.nr + testStream({ + agent, + end, + method: 'createUnzip', + src: Buffer.from(GZIP_CONTENT, 'base64'), + out: Buffer.from(CONTENT).toString('base64') + }) +}) + +test('createDeflate', function (t, end) { + const { agent } = t.nr + testStream({ agent, end, method: 'createDeflate', src: CONTENT, out: DEFLATED_CONTENT }) +}) + +test('createInflate', function (t, end) { + const { agent } = t.nr + testStream({ + agent, + end, + method: 'createInflate', + src: Buffer.from(DEFLATED_CONTENT, 'base64'), + out: Buffer.from(CONTENT).toString('base64') + }) +}) + +test('createDeflateRaw', function (t, end) { + const { agent } = t.nr + testStream({ agent, end, method: 'createDeflateRaw', src: CONTENT, out: DEFLATED_RAW }) +}) + +test('createInflateRaw', function (t, end) { + const { agent } = t.nr + testStream({ + agent, + end, + method: 'createInflateRaw', + src: Buffer.from(DEFLATED_RAW, 'base64'), + out: Buffer.from(CONTENT).toString('base64') + }) +}) + +function testStream({ agent, end, method, src, out }) { + helper.runInTransaction(agent, function (transaction) { + const concatStream = concat(check) + + // The check callback is called when the stream finishes. + const stream = zlib[method]() + stream.pipe(concatStream) + stream.end(src) + + function check(result) { + assert.equal(result.toString('base64'), out, 'should have correct result') + assert.equal(agent.getTransaction(), transaction) + end() + } + }) +} diff --git a/third_party_manifest.json b/third_party_manifest.json index e2aca1cf88..2cfdcc9ddd 100644 --- a/third_party_manifest.json +++ b/third_party_manifest.json @@ -1,5 +1,5 @@ { - "lastUpdated": "Thu Oct 24 2024 13:42:10 GMT-0400 (Eastern Daylight Time)", + "lastUpdated": "Thu Nov 21 2024 17:29:10 GMT-0500 (Eastern Standard Time)", "projectName": "New Relic Node Agent", "projectUrl": "https://github.com/newrelic/node-newrelic", "includeOptDeps": true, @@ -211,56 +211,56 @@ "licenseTextSource": "file", "publisher": "GitHub Inc." }, - "winston-transport@4.8.0": { + "winston-transport@4.7.1": { "name": "winston-transport", - "version": "4.8.0", + "version": "4.7.1", "range": "^4.5.0", "licenses": "MIT", "repoUrl": "https://github.com/winstonjs/winston-transport", - "versionedRepoUrl": "https://github.com/winstonjs/winston-transport/tree/v4.8.0", + "versionedRepoUrl": "https://github.com/winstonjs/winston-transport/tree/v4.7.1", "licenseFile": "node_modules/winston-transport/LICENSE", - "licenseUrl": "https://github.com/winstonjs/winston-transport/blob/v4.8.0/LICENSE", + "licenseUrl": "https://github.com/winstonjs/winston-transport/blob/v4.7.1/LICENSE", "licenseTextSource": "file", "publisher": "Charlie Robbins", "email": "charlie.robbins@gmail.com" } }, "devDependencies": { - "@aws-sdk/client-s3@3.676.0": { + "@aws-sdk/client-s3@3.621.0": { "name": "@aws-sdk/client-s3", - "version": "3.676.0", + "version": "3.621.0", "range": "^3.556.0", "licenses": "Apache-2.0", "repoUrl": "https://github.com/aws/aws-sdk-js-v3", - "versionedRepoUrl": "https://github.com/aws/aws-sdk-js-v3/tree/v3.676.0", + "versionedRepoUrl": "https://github.com/aws/aws-sdk-js-v3/tree/v3.621.0", "licenseFile": "node_modules/@aws-sdk/client-s3/LICENSE", - "licenseUrl": "https://github.com/aws/aws-sdk-js-v3/blob/v3.676.0/LICENSE", + "licenseUrl": "https://github.com/aws/aws-sdk-js-v3/blob/v3.621.0/LICENSE", "licenseTextSource": "file", "publisher": "AWS SDK for JavaScript Team", "url": "https://aws.amazon.com/javascript/" }, - "@aws-sdk/s3-request-presigner@3.676.0": { + "@aws-sdk/s3-request-presigner@3.621.0": { "name": "@aws-sdk/s3-request-presigner", - "version": "3.676.0", + "version": "3.621.0", "range": "^3.556.0", "licenses": "Apache-2.0", "repoUrl": "https://github.com/aws/aws-sdk-js-v3", - "versionedRepoUrl": "https://github.com/aws/aws-sdk-js-v3/tree/v3.676.0", + "versionedRepoUrl": "https://github.com/aws/aws-sdk-js-v3/tree/v3.621.0", "licenseFile": "node_modules/@aws-sdk/s3-request-presigner/LICENSE", - "licenseUrl": "https://github.com/aws/aws-sdk-js-v3/blob/v3.676.0/LICENSE", + "licenseUrl": "https://github.com/aws/aws-sdk-js-v3/blob/v3.621.0/LICENSE", "licenseTextSource": "file", "publisher": "AWS SDK for JavaScript Team", "url": "https://aws.amazon.com/javascript/" }, - "@koa/router@12.0.2": { + "@koa/router@12.0.1": { "name": "@koa/router", - "version": "12.0.2", + "version": "12.0.1", "range": "^12.0.1", "licenses": "MIT", "repoUrl": "https://github.com/koajs/router", - "versionedRepoUrl": "https://github.com/koajs/router/tree/v12.0.2", + "versionedRepoUrl": "https://github.com/koajs/router/tree/v12.0.1", "licenseFile": "node_modules/@koa/router/LICENSE", - "licenseUrl": "https://github.com/koajs/router/blob/v12.0.2/LICENSE", + "licenseUrl": "https://github.com/koajs/router/blob/v12.0.1/LICENSE", "licenseTextSource": "file", "publisher": "Alex Mingoia", "email": "talk@alexmingoia.com" @@ -327,15 +327,15 @@ "licenseUrl": "https://github.com/octokit/rest.js/blob/v18.12.0/LICENSE", "licenseTextSource": "file" }, - "@slack/bolt@3.22.0": { + "@slack/bolt@3.19.0": { "name": "@slack/bolt", - "version": "3.22.0", + "version": "3.19.0", "range": "^3.7.0", "licenses": "MIT", "repoUrl": "https://github.com/slackapi/bolt", - "versionedRepoUrl": "https://github.com/slackapi/bolt/tree/v3.22.0", + "versionedRepoUrl": "https://github.com/slackapi/bolt/tree/v3.19.0", "licenseFile": "node_modules/@slack/bolt/LICENSE", - "licenseUrl": "https://github.com/slackapi/bolt/blob/v3.22.0/LICENSE", + "licenseUrl": "https://github.com/slackapi/bolt/blob/v3.19.0/LICENSE", "licenseTextSource": "file", "publisher": "Slack Technologies, LLC" }, @@ -377,27 +377,27 @@ "licenseTextSource": "file", "publisher": "Evgeny Poberezkin" }, - "async@3.2.6": { + "async@3.2.5": { "name": "async", - "version": "3.2.6", + "version": "3.2.5", "range": "^3.2.4", "licenses": "MIT", "repoUrl": "https://github.com/caolan/async", - "versionedRepoUrl": "https://github.com/caolan/async/tree/v3.2.6", + "versionedRepoUrl": "https://github.com/caolan/async/tree/v3.2.5", "licenseFile": "node_modules/async/LICENSE", - "licenseUrl": "https://github.com/caolan/async/blob/v3.2.6/LICENSE", + "licenseUrl": "https://github.com/caolan/async/blob/v3.2.5/LICENSE", "licenseTextSource": "file", "publisher": "Caolan McMahon" }, - "aws-sdk@2.1691.0": { + "aws-sdk@2.1665.0": { "name": "aws-sdk", - "version": "2.1691.0", + "version": "2.1665.0", "range": "^2.1604.0", "licenses": "Apache-2.0", "repoUrl": "https://github.com/aws/aws-sdk-js", - "versionedRepoUrl": "https://github.com/aws/aws-sdk-js/tree/v2.1691.0", + "versionedRepoUrl": "https://github.com/aws/aws-sdk-js/tree/v2.1665.0", "licenseFile": "node_modules/aws-sdk/LICENSE.txt", - "licenseUrl": "https://github.com/aws/aws-sdk-js/blob/v2.1691.0/LICENSE.txt", + "licenseUrl": "https://github.com/aws/aws-sdk-js/blob/v2.1665.0/LICENSE.txt", "licenseTextSource": "file", "publisher": "Amazon Web Services", "url": "https://aws.amazon.com/" @@ -506,15 +506,15 @@ "publisher": "Michael Radionov", "url": "https://github.com/mradionov" }, - "eslint-plugin-jsdoc@48.11.0": { + "eslint-plugin-jsdoc@48.10.2": { "name": "eslint-plugin-jsdoc", - "version": "48.11.0", + "version": "48.10.2", "range": "^48.0.5", "licenses": "BSD-3-Clause", "repoUrl": "https://github.com/gajus/eslint-plugin-jsdoc", - "versionedRepoUrl": "https://github.com/gajus/eslint-plugin-jsdoc/tree/v48.11.0", + "versionedRepoUrl": "https://github.com/gajus/eslint-plugin-jsdoc/tree/v48.10.2", "licenseFile": "node_modules/eslint-plugin-jsdoc/LICENSE", - "licenseUrl": "https://github.com/gajus/eslint-plugin-jsdoc/blob/v48.11.0/LICENSE", + "licenseUrl": "https://github.com/gajus/eslint-plugin-jsdoc/blob/v48.10.2/LICENSE", "licenseTextSource": "file", "publisher": "Gajus Kuizinas", "email": "gajus@gajus.com", @@ -531,28 +531,28 @@ "licenseUrl": "https://github.com/SonarSource/eslint-plugin-sonarjs/blob/v0.18.0/LICENSE", "licenseTextSource": "file" }, - "eslint@8.57.1": { + "eslint@8.57.0": { "name": "eslint", - "version": "8.57.1", + "version": "8.57.0", "range": "^8.24.0", "licenses": "MIT", "repoUrl": "https://github.com/eslint/eslint", - "versionedRepoUrl": "https://github.com/eslint/eslint/tree/v8.57.1", + "versionedRepoUrl": "https://github.com/eslint/eslint/tree/v8.57.0", "licenseFile": "node_modules/eslint/LICENSE", - "licenseUrl": "https://github.com/eslint/eslint/blob/v8.57.1/LICENSE", + "licenseUrl": "https://github.com/eslint/eslint/blob/v8.57.0/LICENSE", "licenseTextSource": "file", "publisher": "Nicholas C. Zakas", "email": "nicholas+npm@nczconsulting.com" }, - "express@4.21.1": { + "express@4.19.2": { "name": "express", - "version": "4.21.1", + "version": "4.19.2", "range": "*", "licenses": "MIT", "repoUrl": "https://github.com/expressjs/express", - "versionedRepoUrl": "https://github.com/expressjs/express/tree/v4.21.1", + "versionedRepoUrl": "https://github.com/expressjs/express/tree/v4.19.2", "licenseFile": "node_modules/express/LICENSE", - "licenseUrl": "https://github.com/expressjs/express/blob/v4.21.1/LICENSE", + "licenseUrl": "https://github.com/expressjs/express/blob/v4.19.2/LICENSE", "licenseTextSource": "file", "publisher": "TJ Holowaychuk", "email": "tj@vision-media.ca" @@ -609,15 +609,15 @@ "publisher": "Typicode", "email": "typicode@gmail.com" }, - "jsdoc@4.0.4": { + "jsdoc@4.0.3": { "name": "jsdoc", - "version": "4.0.4", + "version": "4.0.3", "range": "^4.0.0", "licenses": "Apache-2.0", "repoUrl": "https://github.com/jsdoc/jsdoc", - "versionedRepoUrl": "https://github.com/jsdoc/jsdoc/tree/v4.0.4", + "versionedRepoUrl": "https://github.com/jsdoc/jsdoc/tree/v4.0.3", "licenseFile": "node_modules/jsdoc/LICENSE.md", - "licenseUrl": "https://github.com/jsdoc/jsdoc/blob/v4.0.4/LICENSE.md", + "licenseUrl": "https://github.com/jsdoc/jsdoc/blob/v4.0.3/LICENSE.md", "licenseTextSource": "file", "publisher": "Michael Mathews", "email": "micmath@gmail.com" @@ -723,15 +723,15 @@ "email": "i@izs.me", "url": "http://blog.izs.me/" }, - "self-cert@2.0.1": { + "self-cert@2.0.0": { "name": "self-cert", - "version": "2.0.1", + "version": "2.0.0", "range": "^2.0.0", "licenses": "MIT", "repoUrl": "https://github.com/jsumners/self-cert", - "versionedRepoUrl": "https://github.com/jsumners/self-cert/tree/v2.0.1", + "versionedRepoUrl": "https://github.com/jsumners/self-cert/tree/v2.0.0", "licenseFile": "node_modules/self-cert/Readme.md", - "licenseUrl": "https://github.com/jsumners/self-cert/blob/v2.0.1/Readme.md", + "licenseUrl": "https://github.com/jsumners/self-cert/blob/v2.0.0/Readme.md", "licenseTextSource": "spdx", "publisher": "James Sumners", "email": "james.sumners@gmail.com" @@ -787,19 +787,6 @@ "publisher": "Isaac Z. Schlueter", "email": "i@izs.me", "url": "http://blog.izs.me" - }, - "temp@0.8.4": { - "name": "temp", - "version": "0.8.4", - "range": "^0.8.1", - "licenses": "MIT", - "repoUrl": "https://github.com/bruce/node-temp", - "versionedRepoUrl": "https://github.com/bruce/node-temp/tree/v0.8.4", - "licenseFile": "node_modules/temp/LICENSE", - "licenseUrl": "https://github.com/bruce/node-temp/blob/v0.8.4/LICENSE", - "licenseTextSource": "file", - "publisher": "Bruce Williams", - "email": "brwcodes@gmail.com" } } }