From d9147a0770c932726ff2a4180cefaa16f536f835 Mon Sep 17 00:00:00 2001 From: Bob Evans Date: Tue, 1 Oct 2024 15:41:48 -0400 Subject: [PATCH] test: Migrated last group of unit tests to `node:test` --- test/unit/timer.test.js | 141 ++-- test/unit/trace-segment.test.js | 644 ----------------- test/unit/tracer.test.js | 145 ---- .../trace/index.test.js} | 650 +++++++++--------- test/unit/transaction/trace/segment.test.js | 627 +++++++++++++++++ .../trace}/trace-aggregator.test.js | 259 ++++--- test/unit/transaction/tracer.test.js | 145 ++++ 7 files changed, 1282 insertions(+), 1329 deletions(-) delete mode 100644 test/unit/trace-segment.test.js delete mode 100644 test/unit/tracer.test.js rename test/unit/{trace.test.js => transaction/trace/index.test.js} (60%) create mode 100644 test/unit/transaction/trace/segment.test.js rename test/unit/{ => transaction/trace}/trace-aggregator.test.js (57%) create mode 100644 test/unit/transaction/tracer.test.js diff --git a/test/unit/timer.test.js b/test/unit/timer.test.js index 9bbf8eee4b..e8ca28db28 100644 --- a/test/unit/timer.test.js +++ b/test/unit/timer.test.js @@ -4,87 +4,79 @@ */ 'use strict' -const tap = require('tap') +const assert = require('node:assert') +const test = require('node:test') const Timer = require('../../lib/timer') -tap.test('Timer', function (t) { - t.autoend() - t.test("should know when it's active", function (t) { +test('Timer', async function (t) { + await t.test("should know when it's active", function () { const timer = new Timer() - t.equal(timer.isActive(), true) - t.end() + assert.equal(timer.isActive(), true) }) - t.test("should know when it hasn't yet been started", function (t) { + await t.test("should know when it hasn't yet been started", function () { const timer = new Timer() - t.equal(timer.isRunning(), false) - t.end() + assert.equal(timer.isRunning(), false) }) - t.test("should know when it's running", function (t) { + await t.test("should know when it's running", function () { const timer = new Timer() timer.begin() - t.equal(timer.isRunning(), true) - t.end() + assert.equal(timer.isRunning(), true) }) - t.test("should know when it's not running", function (t) { + await t.test("should know when it's not running", function () { const timer = new Timer() - t.equal(timer.isRunning(), false) + assert.equal(timer.isRunning(), false) timer.begin() timer.end() - t.equal(timer.isRunning(), false) - t.end() + assert.equal(timer.isRunning(), false) }) - t.test("should know when it hasn't yet been stopped", function (t) { + await t.test("should know when it hasn't yet been stopped", function () { const timer = new Timer() - t.equal(timer.isActive(), true) + assert.equal(timer.isActive(), true) timer.begin() - t.equal(timer.isActive(), true) - t.end() + assert.equal(timer.isActive(), true) }) - t.test("should know when it's stopped", function (t) { + await t.test("should know when it's stopped", function () { const timer = new Timer() timer.begin() timer.end() - t.equal(timer.isActive(), false) - t.end() + assert.equal(timer.isActive(), false) }) - t.test('should return the time elapsed of a running timer', function (t) { + await t.test('should return the time elapsed of a running timer', function (t, end) { const timer = new Timer() timer.begin() setTimeout(function () { - t.ok(timer.getDurationInMillis() > 3) + assert.ok(timer.getDurationInMillis() > 3) - t.end() + end() }, 5) }) - t.test('should allow setting the start as well as the duration of the range', function (t) { + await t.test('should allow setting the start as well as the duration of the range', function () { const timer = new Timer() const start = Date.now() timer.setDurationInMillis(5, start) - t.equal(timer.start, start) - t.end() + assert.equal(timer.start, start) }) - t.test('should return a range object', function (t) { + await t.test('should return a range object', function () { const timer = new Timer() const start = Date.now() timer.setDurationInMillis(5, start) - t.same(timer.toRange(), [start, start + 5]) - t.end() + assert.deepEqual(timer.toRange(), [start, start + 5]) }) - t.test('should calculate start times relative to other timers', function (t) { + await t.test('should calculate start times relative to other timers', function (t, end) { const first = new Timer() first.begin() @@ -95,14 +87,14 @@ tap.test('Timer', function (t) { second.end() let delta - t.doesNotThrow(function () { + assert.doesNotThrow(function () { delta = second.startedRelativeTo(first) }) - t.ok(typeof delta === 'number') - t.end() + assert.ok(typeof delta === 'number') + end() }) - t.test('should support updating the duration with touch', function (t) { + await t.test('should support updating the duration with touch', function (t, end) { const timer = new Timer() timer.begin() @@ -110,88 +102,81 @@ tap.test('Timer', function (t) { timer.touch() const first = timer.getDurationInMillis() - t.ok(first > 0) - t.equal(timer.isActive(), true) + assert.ok(first > 0) + assert.equal(timer.isActive(), true) setTimeout(function () { timer.end() const second = timer.getDurationInMillis() - t.ok(second > first) - t.equal(timer.isActive(), false) + assert.ok(second > first) + assert.equal(timer.isActive(), false) - t.end() + end() }, 20) }, 20) }) - t.test('endsAfter indicates whether the timer ended after another timer', (t) => { - t.autoend() - t.beforeEach(function (t) { + await t.test('endsAfter indicates whether the timer ended after another timer', async (t) => { + t.beforeEach(function (ctx) { + ctx.nr = {} const start = Date.now() const first = new Timer() first.setDurationInMillis(10, start) - t.context.second = new Timer() - t.context.start = start - t.context.first = first + ctx.nr.second = new Timer() + ctx.nr.start = start + ctx.nr.first = first }) - t.test('with the same start and duration', function (t) { - const { start, second, first } = t.context + await t.test('with the same start and duration', function (t) { + const { start, second, first } = t.nr second.setDurationInMillis(10, start) - t.equal(second.endsAfter(first), false) - t.end() + assert.equal(second.endsAfter(first), false) }) - t.test('with longer duration', function (t) { - const { start, second, first } = t.context + await t.test('with longer duration', function (t) { + const { start, second, first } = t.nr second.setDurationInMillis(11, start) - t.equal(second.endsAfter(first), true) - t.end() + assert.equal(second.endsAfter(first), true) }) - t.test('with shorter duration', function (t) { - const { start, second, first } = t.context + await t.test('with shorter duration', function (t) { + const { start, second, first } = t.nr second.setDurationInMillis(9, start) - t.equal(second.endsAfter(first), false) - t.end() + assert.equal(second.endsAfter(first), false) }) - t.test('with earlier start', function (t) { - const { start, second, first } = t.context + await t.test('with earlier start', function (t) { + const { start, second, first } = t.nr second.setDurationInMillis(10, start - 1) - t.equal(second.endsAfter(first), false) - t.end() + assert.equal(second.endsAfter(first), false) }) - t.test('with later start', function (t) { - const { start, second, first } = t.context + await t.test('with later start', function (t) { + const { start, second, first } = t.nr second.setDurationInMillis(10, start + 1) - t.equal(second.endsAfter(first), true) - t.end() + assert.equal(second.endsAfter(first), true) }) }) - t.test('overwriteDurationInMillis', function (t) { - t.autoend() - t.test('stops the timer', function (t) { + await t.test('overwriteDurationInMillis', async function (t) { + await t.test('stops the timer', function () { const timer = new Timer() timer.begin() - t.equal(timer.isActive(), true) + assert.equal(timer.isActive(), true) timer.overwriteDurationInMillis(10) - t.equal(timer.isActive(), false) - t.end() + assert.equal(timer.isActive(), false) }) - t.test('overwrites duration recorded by end() and touch()', function (t) { + await t.test('overwrites duration recorded by end() and touch()', function (t, end) { const timer = new Timer() timer.begin() setTimeout(function () { - t.equal(timer.getDurationInMillis() > 1, true) + assert.equal(timer.getDurationInMillis() > 1, true) timer.overwriteDurationInMillis(1) - t.equal(timer.getDurationInMillis(), 1) - t.end() + assert.equal(timer.getDurationInMillis(), 1) + end() }, 2) }) }) diff --git a/test/unit/trace-segment.test.js b/test/unit/trace-segment.test.js deleted file mode 100644 index 236a25bdd1..0000000000 --- a/test/unit/trace-segment.test.js +++ /dev/null @@ -1,644 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -/* eslint dot-notation: off */ -'use strict' - -const tap = require('tap') -const DESTINATIONS = require('../../lib/config/attribute-filter').DESTINATIONS -const sinon = require('sinon') -const helper = require('../lib/agent_helper') -const TraceSegment = require('../../lib/transaction/trace/segment') -const Transaction = require('../../lib/transaction') - -tap.test('TraceSegment', (t) => { - t.autoend() - let agent = null - - t.beforeEach(() => { - if (agent === null) { - agent = helper.loadMockedAgent() - } - }) - - t.afterEach(() => { - if (agent) { - helper.unloadAgent(agent) - agent = null - } - }) - - t.test('should be bound to a Trace', (t) => { - let segment = null - const trans = new Transaction(agent) - t.throws(function noTrace() { - segment = new TraceSegment(null, 'UnitTest') - }) - t.equal(segment, null) - - const success = new TraceSegment(trans, 'UnitTest') - t.equal(success.transaction, trans) - trans.end() - t.end() - }) - - t.test('should not add new children when marked as opaque', (t) => { - const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'UnitTest') - t.notOk(segment.opaque) - segment.opaque = true - segment.add('child') - t.equal(segment.children.length, 0) - segment.opaque = false - segment.add('child') - t.equal(segment.children.length, 1) - trans.end() - t.end() - }) - - t.test('should call an optional callback function', (t) => { - const trans = new Transaction(agent) - t.doesNotThrow(function noCallback() { - new TraceSegment(trans, 'UnitTest') // eslint-disable-line no-new - }) - const working = new TraceSegment(trans, 'UnitTest', t.end) - working.end() - trans.end() - }) - - t.test('has a name', (t) => { - const trans = new Transaction(agent) - const success = new TraceSegment(trans, 'UnitTest') - t.equal(success.name, 'UnitTest') - t.end() - }) - - t.test('is created with no children', (t) => { - const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'UnitTest') - t.equal(segment.children.length, 0) - t.end() - }) - - t.test('has a timer', (t) => { - const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'UnitTest') - t.ok(segment.timer) - t.end() - }) - - t.test('does not start its timer on creation', (t) => { - const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'UnitTest') - t.equal(segment.timer.isRunning(), false) - t.end() - }) - - t.test('allows the timer to be updated without ending it', (t) => { - const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'UnitTest') - segment.start() - segment.touch() - t.equal(segment.timer.isRunning(), true) - t.ok(segment.getDurationInMillis() > 0) - t.end() - }) - - t.test('accepts a callback that records metrics for this segment', (t) => { - const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'Test', (insider) => { - t.equal(insider, segment) - return t.end() - }) - segment.end() - trans.end() - }) - - t.test('#getSpanId', (t) => { - t.autoend() - - t.test('should return the segment id when dt and spans are enabled', (t) => { - const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'Test') - agent.config.distributed_tracing.enabled = true - agent.config.span_events.enabled = true - t.equal(segment.getSpanId(), segment.id) - t.end() - }) - - t.test('should return null when dt is disabled', (t) => { - const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'Test') - agent.config.distributed_tracing.enabled = false - agent.config.span_events.enabled = true - t.equal(segment.getSpanId(), null) - t.end() - }) - - t.test('should return null when spans are disabled', (t) => { - const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'Test') - agent.config.distributed_tracing.enabled = true - agent.config.span_events.enabled = false - t.ok(segment.getSpanId() === null) - t.end() - }) - }) - - t.test('updates root segment timer when end() is called', (t) => { - const trans = new Transaction(agent) - const trace = trans.trace - const segment = new TraceSegment(trans, 'Test') - - segment.setDurationInMillis(10, 0) - - setTimeout(() => { - t.equal(trace.root.timer.hrDuration, null) - segment.end() - t.ok(trace.root.timer.getDurationInMillis() > segment.timer.getDurationInMillis() - 1) // alow for slop - t.end() - }, 10) - }) - - t.test('properly tracks the number of active or harvested segments', (t) => { - t.equal(agent.activeTransactions, 0) - t.equal(agent.totalActiveSegments, 0) - t.equal(agent.segmentsCreatedInHarvest, 0) - - const tx = new Transaction(agent) - t.equal(agent.totalActiveSegments, 1) - t.equal(agent.segmentsCreatedInHarvest, 1) - t.equal(tx.numSegments, 1) - t.equal(agent.activeTransactions, 1) - - const segment = new TraceSegment(tx, 'Test') // eslint-disable-line no-unused-vars - t.equal(agent.totalActiveSegments, 2) - t.equal(agent.segmentsCreatedInHarvest, 2) - t.equal(tx.numSegments, 2) - tx.end() - - t.equal(agent.activeTransactions, 0) - - setTimeout(function () { - t.equal(agent.totalActiveSegments, 0) - t.equal(agent.segmentsClearedInHarvest, 2) - - agent.forceHarvestAll(() => { - t.equal(agent.totalActiveSegments, 0) - t.equal(agent.segmentsClearedInHarvest, 0) - t.equal(agent.segmentsCreatedInHarvest, 0) - t.end() - }) - }, 10) - }) - - t.test('toJSON should not modify attributes', (t) => { - const transaction = new Transaction(agent) - const segment = new TraceSegment(transaction, 'TestSegment') - segment.toJSON() - t.same(segment.getAttributes(), {}) - t.end() - }) - - t.test('with children created from URLs', (t) => { - t.autoend() - let webChild - - t.beforeEach(() => { - agent.config.attributes.enabled = true - agent.config.attributes.include.push('request.parameters.*') - agent.config.emit('attributes.include') - - const transaction = new Transaction(agent) - const trace = transaction.trace - const segment = trace.add('UnitTest') - - const url = '/test?test1=value1&test2&test3=50&test4=' - - webChild = segment.add(url) - transaction.baseSegment = webChild - transaction.finalizeNameFromUri(url, 200) - - trace.setDurationInMillis(1, 0) - webChild.setDurationInMillis(1, 0) - - trace.end() - }) - - t.test('should return the URL minus any query parameters', (t) => { - t.equal(webChild.name, 'WebTransaction/NormalizedUri/*') - t.end() - }) - - t.test('should have attributes on the child segment', (t) => { - t.ok(webChild.getAttributes()) - t.end() - }) - - t.test('should have the parameters that were passed in the query string', (t) => { - const attributes = webChild.getAttributes() - t.equal(attributes['request.parameters.test1'], 'value1') - t.equal(attributes['request.parameters.test3'], '50') - t.end() - }) - - t.test('should set bare parameters to true (as in present)', (t) => { - t.equal(webChild.getAttributes()['request.parameters.test2'], true) - t.end() - }) - - t.test('should set parameters with empty values to ""', (t) => { - t.equal(webChild.getAttributes()['request.parameters.test4'], '') - t.end() - }) - - t.test('should serialize the segment with the parameters', (t) => { - t.same(webChild.toJSON(), [ - 0, - 1, - 'WebTransaction/NormalizedUri/*', - { - 'nr_exclusive_duration_millis': 1, - 'request.parameters.test1': 'value1', - 'request.parameters.test2': true, - 'request.parameters.test3': '50', - 'request.parameters.test4': '' - }, - [] - ]) - t.end() - }) - }) - - t.test('with parameters parsed out by framework', (t) => { - t.autoend() - let webChild - let trace - - t.beforeEach(() => { - agent.config.attributes.enabled = true - - const transaction = new Transaction(agent) - trace = transaction.trace - trace.mer = 6 - - const segment = trace.add('UnitTest') - - const url = '/test' - const params = {} - - // Express uses positional parameters sometimes - params[0] = 'first' - params[1] = 'another' - params.test3 = '50' - - webChild = segment.add(url) - transaction.trace.attributes.addAttributes(DESTINATIONS.TRANS_SCOPE, params) - transaction.baseSegment = webChild - transaction.finalizeNameFromUri(url, 200) - - trace.setDurationInMillis(1, 0) - webChild.setDurationInMillis(1, 0) - - trace.end() - }) - - t.test('should return the URL minus any query parameters', (t) => { - t.equal(webChild.name, 'WebTransaction/NormalizedUri/*') - t.end() - }) - - t.test('should have attributes on the trace', (t) => { - t.ok(trace.attributes.get(DESTINATIONS.TRANS_TRACE)) - t.end() - }) - - t.test('should have the positional parameters from the params array', (t) => { - const attributes = trace.attributes.get(DESTINATIONS.TRANS_TRACE) - t.equal(attributes[0], 'first') - t.equal(attributes[1], 'another') - t.end() - }) - - t.test('should have the named parameter from the params array', (t) => { - t.equal(trace.attributes.get(DESTINATIONS.TRANS_TRACE)['test3'], '50') - t.end() - }) - - t.test('should serialize the segment with the parameters', (t) => { - const expected = [ - 0, - 1, - 'WebTransaction/NormalizedUri/*', - { - nr_exclusive_duration_millis: 1, - 0: 'first', - 1: 'another', - test3: '50' - }, - [] - ] - t.same(webChild.toJSON(), expected) - t.end() - }) - }) - - t.test('with attributes.enabled set to false', (t) => { - t.autoend() - let webChild - - t.beforeEach(() => { - agent.config.attributes.enabled = false - - const transaction = new Transaction(agent) - const trace = transaction.trace - const segment = new TraceSegment(transaction, 'UnitTest') - const url = '/test?test1=value1&test2&test3=50&test4=' - - webChild = segment.add(url) - webChild.addAttribute('test', 'non-null value') - transaction.baseSegment = webChild - transaction.finalizeNameFromUri(url, 200) - - trace.setDurationInMillis(1, 0) - webChild.setDurationInMillis(1, 0) - }) - - t.test('should return the URL minus any query parameters', (t) => { - t.equal(webChild.name, 'WebTransaction/NormalizedUri/*') - t.end() - }) - - t.test('should have no attributes on the child segment', (t) => { - t.same(webChild.getAttributes(), {}) - t.end() - }) - - t.test('should serialize the segment without the parameters', (t) => { - const expected = [0, 1, 'WebTransaction/NormalizedUri/*', {}, []] - t.same(webChild.toJSON(), expected) - t.end() - }) - }) - - t.test('with attributes.enabled set', (t) => { - t.autoend() - let webChild - let attributes = null - - t.beforeEach(() => { - agent.config.attributes.enabled = true - agent.config.attributes.include = ['request.parameters.*'] - agent.config.attributes.exclude = ['request.parameters.test1', 'request.parameters.test4'] - agent.config.emit('attributes.exclude') - - const transaction = new Transaction(agent) - const trace = transaction.trace - const segment = trace.add('UnitTest') - - const url = '/test?test1=value1&test2&test3=50&test4=' - - webChild = segment.add(url) - transaction.baseSegment = webChild - transaction.finalizeNameFromUri(url, 200) - webChild.markAsWeb(url) - - trace.setDurationInMillis(1, 0) - webChild.setDurationInMillis(1, 0) - attributes = webChild.getAttributes() - - trace.end() - }) - - t.test('should return the URL minus any query parameters', (t) => { - t.equal(webChild.name, 'WebTransaction/NormalizedUri/*') - t.end() - }) - - t.test('should have attributes on the child segment', (t) => { - t.ok(attributes) - t.end() - }) - - t.test('should filter the parameters that were passed in the query string', (t) => { - t.equal(attributes['test1'], undefined) - t.equal(attributes['request.parameters.test1'], undefined) - - t.equal(attributes['test3'], undefined) - t.equal(attributes['request.parameters.test3'], '50') - - t.equal(attributes['test4'], undefined) - t.equal(attributes['request.parameters.test4'], undefined) - t.end() - }) - - t.test('should set bare parameters to true (as in present)', (t) => { - t.equal(attributes['test2'], undefined) - t.equal(attributes['request.parameters.test2'], true) - t.end() - }) - - t.test('should serialize the segment with the parameters', (t) => { - t.same(webChild.toJSON(), [ - 0, - 1, - 'WebTransaction/NormalizedUri/*', - { - 'nr_exclusive_duration_millis': 1, - 'request.parameters.test2': true, - 'request.parameters.test3': '50' - }, - [] - ]) - t.end() - }) - }) - - t.test('when ended', (t) => { - t.autoend() - - t.test('stops its timer', (t) => { - const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'UnitTest') - segment.end() - t.equal(segment.timer.isRunning(), false) - t.end() - }) - - t.test('should produce JSON that conforms to the collector spec', (t) => { - const transaction = new Transaction(agent) - const trace = transaction.trace - const segment = trace.add('DB/select/getSome') - - trace.setDurationInMillis(17, 0) - segment.setDurationInMillis(14, 3) - - trace.end() - - // See documentation on TraceSegment.toJSON for what goes in which field. - t.same(segment.toJSON(), [ - 3, - 17, - 'DB/select/getSome', - { nr_exclusive_duration_millis: 14 }, - [] - ]) - t.end() - }) - }) - - t.test('#finalize', (t) => { - t.autoend() - - t.test('should add nr_exclusive_duration_millis attribute', (t) => { - const transaction = new Transaction(agent) - const segment = new TraceSegment(transaction, 'TestSegment') - - segment._setExclusiveDurationInMillis(1) - - t.same(segment.getAttributes(), {}) - - segment.finalize() - - t.equal(segment.getAttributes()['nr_exclusive_duration_millis'], 1) - t.end() - }) - - t.test('should truncate when timer still running', (t) => { - const segmentName = 'TestSegment' - - const transaction = new Transaction(agent) - const segment = new TraceSegment(transaction, segmentName) - - // Force truncation - sinon.stub(segment.timer, 'softEnd').returns(true) - sinon.stub(segment.timer, 'endsAfter').returns(true) - - const root = transaction.trace.root - - // Make root duration calculation predictable - root.timer.start = 1000 - segment.timer.start = 1001 - segment.overwriteDurationInMillis(3) - - segment.finalize() - - t.equal(segment.name, `Truncated/${segmentName}`) - t.equal(root.getDurationInMillis(), 4) - t.end() - }) - }) -}) - -tap.test('when serialized', (t) => { - t.autoend() - - let agent = null - let trans = null - let segment = null - - t.beforeEach(() => { - agent = helper.loadMockedAgent() - trans = new Transaction(agent) - segment = new TraceSegment(trans, 'UnitTest') - }) - - t.afterEach(() => { - helper.unloadAgent(agent) - agent = null - trans = null - segment = null - }) - - t.test('should create a plain JS array', (t) => { - segment.end() - const js = segment.toJSON() - - t.ok(Array.isArray(js)) - t.equal(typeof js[0], 'number') - t.equal(typeof js[1], 'number') - - t.equal(js[2], 'UnitTest') - - t.equal(typeof js[3], 'object') - - t.ok(Array.isArray(js[4])) - t.equal(js[4].length, 0) - - t.end() - }) - - t.test('should not cause a stack overflow', { timeout: 30000 }, (t) => { - let parent = segment - for (let i = 0; i < 9000; ++i) { - const child = new TraceSegment(trans, 'Child ' + i) - parent.children.push(child) - parent = child - } - - t.doesNotThrow(function () { - segment.toJSON() - }) - - t.end() - }) -}) - -tap.test('getSpanContext', (t) => { - t.autoend() - - let agent = null - let transaction = null - let segment = null - - t.beforeEach(() => { - agent = helper.loadMockedAgent({ - distributed_tracing: { - enabled: true - } - }) - transaction = new Transaction(agent) - segment = new TraceSegment(transaction, 'UnitTest') - }) - - t.afterEach(() => { - helper.unloadAgent(agent) - agent = null - transaction = null - segment = null - }) - - t.test('should not initialize with a span context', (t) => { - t.notOk(segment._spanContext) - t.end() - }) - - t.test('should create a new context when empty', (t) => { - const spanContext = segment.getSpanContext() - t.ok(spanContext) - t.end() - }) - - t.test('should not create a new context when empty and DT disabled', (t) => { - agent.config.distributed_tracing.enabled = false - const spanContext = segment.getSpanContext() - t.notOk(spanContext) - t.end() - }) - - t.test('should not create a new context when empty and Spans disabled', (t) => { - agent.config.span_events.enabled = false - const spanContext = segment.getSpanContext() - t.notOk(spanContext) - t.end() - }) - - t.test('should return existing span context', (t) => { - const originalContext = segment.getSpanContext() - const secondContext = segment.getSpanContext() - t.equal(originalContext, secondContext) - t.end() - }) -}) diff --git a/test/unit/tracer.test.js b/test/unit/tracer.test.js deleted file mode 100644 index 6cc057056b..0000000000 --- a/test/unit/tracer.test.js +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' -const tap = require('tap') -const helper = require('../lib/agent_helper') -const Segment = require('../../lib/transaction/trace/segment') - -const notRunningStates = ['stopped', 'stopping', 'errored'] -function beforeEach(t) { - const agent = helper.loadMockedAgent() - t.context.tracer = agent.tracer - t.context.agent = agent -} - -function afterEach(t) { - helper.unloadAgent(t.context.agent) -} - -tap.test('Tracer', function (t) { - t.autoend() - - t.test('#transactionProxy', (t) => { - t.autoend() - t.beforeEach(beforeEach) - t.afterEach(afterEach) - t.test('should create transaction', (t) => { - const { tracer } = t.context - const wrapped = tracer.transactionProxy(() => { - const transaction = tracer.getTransaction() - t.ok(transaction) - t.end() - }) - - wrapped() - }) - - t.test('should not try to wrap a null handler', function (t) { - const { tracer } = t.context - t.equal(tracer.transactionProxy(null), null) - t.end() - }) - - notRunningStates.forEach((agentState) => { - t.test(`should not create transaction when agent state is ${agentState}`, (t) => { - const { tracer, agent } = t.context - agent.setState(agentState) - - const wrapped = tracer.transactionProxy(() => { - const transaction = tracer.getTransaction() - t.notOk(transaction) - }) - - wrapped() - t.end() - }) - }) - }) - - t.test('#transactionNestProxy', (t) => { - t.autoend() - t.beforeEach(beforeEach) - t.afterEach(afterEach) - t.test('should create transaction', (t) => { - const { tracer } = t.context - const wrapped = tracer.transactionNestProxy('web', () => { - const transaction = tracer.getTransaction() - t.ok(transaction) - }) - - wrapped() - t.end() - }) - - notRunningStates.forEach((agentState) => { - t.test(`should not create transaction when agent state is ${agentState}`, (t) => { - const { tracer, agent } = t.context - agent.setState(agentState) - - const wrapped = tracer.transactionNestProxy('web', () => { - const transaction = tracer.getTransaction() - t.notOk(transaction) - }) - - wrapped() - t.end() - }) - }) - - t.test('when proxying a trace segment should not try to wrap a null handler', function (t) { - const { tracer, agent } = t.context - helper.runInTransaction(agent, function () { - t.equal(tracer.wrapFunction('123', null, null), null) - t.end() - }) - }) - - t.test('when proxying a callback should not try to wrap a null handler', function (t) { - const { tracer, agent } = t.context - helper.runInTransaction(agent, function () { - t.equal(tracer.bindFunction(null), null) - t.end() - }) - }) - - t.test('when handling immutable errors should not break in annotation process', function (t) { - const expectErrMsg = 'FIREBOMB' - const { tracer, agent } = t.context - helper.runInTransaction(agent, function (trans) { - function wrapMe() { - const err = new Error(expectErrMsg) - Object.freeze(err) - throw err - } - try { - // cannot use `t.throws` because we instrument things within the function - // so the original throws then another throws and tap does not like that - const fn = tracer.bindFunction(wrapMe, new Segment(trans, 'name')) - fn() - } catch (err) { - t.equal(err.message, expectErrMsg) - t.end() - } - }) - }) - - t.test( - 'when a transaction is created inside a transaction should reuse the existing transaction instead of nesting', - function (t) { - const { agent } = t.context - helper.runInTransaction(agent, function (outerTransaction) { - const outerId = outerTransaction.id - helper.runInTransaction(agent, function (innerTransaction) { - const innerId = innerTransaction.id - - t.equal(innerId, outerId) - t.end() - }) - }) - } - ) - }) -}) diff --git a/test/unit/trace.test.js b/test/unit/transaction/trace/index.test.js similarity index 60% rename from test/unit/trace.test.js rename to test/unit/transaction/trace/index.test.js index 82ed017733..b3641a0cd9 100644 --- a/test/unit/trace.test.js +++ b/test/unit/transaction/trace/index.test.js @@ -4,84 +4,81 @@ */ 'use strict' - -const tap = require('tap') - +const assert = require('node:assert') +const test = require('node:test') const util = require('util') const sinon = require('sinon') -const DESTINATIONS = require('../../lib/config/attribute-filter').DESTINATIONS -const helper = require('../lib/agent_helper') -const codec = require('../../lib/util/codec') +const DESTINATIONS = require('../../../../lib/config/attribute-filter').DESTINATIONS +const helper = require('../../../lib/agent_helper') +const codec = require('../../../../lib/util/codec') const codecEncodeAsync = util.promisify(codec.encode) const codecDecodeAsync = util.promisify(codec.decode) -const Segment = require('../../lib/transaction/trace/segment') -const DTPayload = require('../../lib/transaction/dt-payload') -const Trace = require('../../lib/transaction/trace') -const Transaction = require('../../lib/transaction') +const Segment = require('../../../../lib/transaction/trace/segment') +const DTPayload = require('../../../../lib/transaction/dt-payload') +const Trace = require('../../../../lib/transaction/trace') +const Transaction = require('../../../../lib/transaction') const NEWRELIC_TRACE_HEADER = 'newrelic' -tap.test('Trace', (t) => { - t.autoend() - let agent = null - - t.beforeEach(() => { - agent = helper.loadMockedAgent() +test('Trace', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.loadMockedAgent() }) - t.afterEach(() => { - helper.unloadAgent(agent) + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - t.test('should always be bound to a transaction', (t) => { - // fail - t.throws(() => { + await t.test('should always be bound to a transaction', (t) => { + const { agent } = t.nr + assert.throws(() => { return new Trace() }, /must be associated with a transaction/) - // succeed const transaction = new Transaction(agent) const tt = new Trace(transaction) - t.type(tt.transaction, Transaction) - t.end() + assert.ok(tt.transaction instanceof Transaction) }) - t.test('should have the root of a Segment tree', (t) => { + await t.test('should have the root of a Segment tree', (t) => { + const { agent } = t.nr const tt = new Trace(new Transaction(agent)) - t.type(tt.root, Segment) - t.end() + assert.ok(tt.root instanceof Segment) }) - t.test('should be the primary interface for adding segments to a trace', (t) => { + await t.test('should be the primary interface for adding segments to a trace', (t) => { + const { agent } = t.nr const transaction = new Transaction(agent) const trace = transaction.trace - t.doesNotThrow(() => { + assert.doesNotThrow(() => { trace.add('Custom/Test17/Child1') }) - t.end() }) - t.test('should have DT attributes on transaction end', (t) => { + await t.test('should have DT attributes on transaction end', (t, end) => { + const { agent } = t.nr agent.config.distributed_tracing.enabled = true agent.config.primary_application_id = 'test' agent.config.account_id = 1 helper.runInTransaction(agent, function (tx) { tx.end() const attributes = tx.trace.intrinsics - t.equal(attributes.traceId, tx.traceId) - t.equal(attributes.guid, tx.id) - t.equal(attributes.priority, tx.priority) - t.equal(attributes.sampled, tx.sampled) - t.equal(attributes.parentId, undefined) - t.equal(attributes.parentSpanId, undefined) - t.equal(tx.sampled, true) - t.ok(tx.priority > 1) - t.end() + assert.equal(attributes.traceId, tx.traceId) + assert.equal(attributes.guid, tx.id) + assert.equal(attributes.priority, tx.priority) + assert.equal(attributes.sampled, tx.sampled) + assert.equal(attributes.parentId, undefined) + assert.equal(attributes.parentSpanId, undefined) + assert.equal(tx.sampled, true) + assert.ok(tx.priority > 1) + end() }) }) - t.test('should have DT parent attributes on payload accept', (t) => { + await t.test('should have DT parent attributes on payload accept', (t, end) => { + const { agent } = t.nr agent.config.distributed_tracing.enabled = true agent.config.primary_application_id = 'test' agent.config.account_id = 1 @@ -91,22 +88,23 @@ tap.test('Trace', (t) => { tx._acceptDistributedTracePayload(payload) tx.end() const attributes = tx.trace.intrinsics - t.equal(attributes.traceId, tx.traceId) - t.equal(attributes.guid, tx.id) - t.equal(attributes.priority, tx.priority) - t.equal(attributes.sampled, tx.sampled) - t.equal(attributes['parent.type'], 'App') - t.equal(attributes['parent.app'], agent.config.primary_application_id) - t.equal(attributes['parent.account'], agent.config.account_id) - t.equal(attributes.parentId, undefined) - t.equal(attributes.parentSpanId, undefined) - t.equal(tx.sampled, true) - t.ok(tx.priority > 1) - t.end() + assert.equal(attributes.traceId, tx.traceId) + assert.equal(attributes.guid, tx.id) + assert.equal(attributes.priority, tx.priority) + assert.equal(attributes.sampled, tx.sampled) + assert.equal(attributes['parent.type'], 'App') + assert.equal(attributes['parent.app'], agent.config.primary_application_id) + assert.equal(attributes['parent.account'], agent.config.account_id) + assert.equal(attributes.parentId, undefined) + assert.equal(attributes.parentSpanId, undefined) + assert.equal(tx.sampled, true) + assert.ok(tx.priority > 1) + end() }) }) - t.test('should generate span events', (t) => { + await t.test('should generate span events', (t) => { + const { agent } = t.nr agent.config.span_events.enabled = true agent.config.distributed_tracing.enabled = true @@ -126,46 +124,45 @@ tap.test('Trace', (t) => { const events = agent.spanEventAggregator.getEvents() const nested = events[0] const testSpan = events[1] - t.hasProp(nested, 'intrinsics') - t.hasProp(testSpan, 'intrinsics') - - t.hasProp(nested.intrinsics, 'parentId') - t.equal(nested.intrinsics.parentId, testSpan.intrinsics.guid) - t.hasProp(nested.intrinsics, 'category') - t.equal(nested.intrinsics.category, 'generic') - t.hasProp(nested.intrinsics, 'priority') - t.equal(nested.intrinsics.priority, transaction.priority) - t.hasProp(nested.intrinsics, 'transactionId') - t.equal(nested.intrinsics.transactionId, transaction.id) - t.hasProp(nested.intrinsics, 'sampled') - t.equal(nested.intrinsics.sampled, transaction.sampled) - t.hasProp(nested.intrinsics, 'name') - t.equal(nested.intrinsics.name, 'nested') - t.hasProp(nested.intrinsics, 'traceId') - t.equal(nested.intrinsics.traceId, transaction.traceId) - t.hasProp(nested.intrinsics, 'timestamp') - - t.hasProp(testSpan.intrinsics, 'parentId') - t.equal(testSpan.intrinsics.parentId, null) - t.hasProp(testSpan.intrinsics, 'nr.entryPoint') - t.ok(testSpan.intrinsics['nr.entryPoint']) - t.hasProp(testSpan.intrinsics, 'category') - t.equal(testSpan.intrinsics.category, 'generic') - t.hasProp(testSpan.intrinsics, 'priority') - t.equal(testSpan.intrinsics.priority, transaction.priority) - t.hasProp(testSpan.intrinsics, 'transactionId') - t.equal(testSpan.intrinsics.transactionId, transaction.id) - t.hasProp(testSpan.intrinsics, 'sampled') - t.equal(testSpan.intrinsics.sampled, transaction.sampled) - t.hasProp(testSpan.intrinsics, 'name') - t.equal(testSpan.intrinsics.name, 'test') - t.hasProp(testSpan.intrinsics, 'traceId') - t.equal(testSpan.intrinsics.traceId, transaction.traceId) - t.hasProp(testSpan.intrinsics, 'timestamp') - t.end() + assert.ok(nested.intrinsics) + assert.ok(testSpan.intrinsics) + + assert.ok(nested.intrinsics.parentId) + assert.equal(nested.intrinsics.parentId, testSpan.intrinsics.guid) + assert.ok(nested.intrinsics.category) + assert.equal(nested.intrinsics.category, 'generic') + assert.ok(nested.intrinsics.priority) + assert.equal(nested.intrinsics.priority, transaction.priority) + assert.ok(nested.intrinsics.transactionId) + assert.equal(nested.intrinsics.transactionId, transaction.id) + assert.ok(nested.intrinsics.sampled) + assert.equal(nested.intrinsics.sampled, transaction.sampled) + assert.ok(nested.intrinsics.name) + assert.equal(nested.intrinsics.name, 'nested') + assert.ok(nested.intrinsics.traceId) + assert.equal(nested.intrinsics.traceId, transaction.traceId) + assert.ok(nested.intrinsics.timestamp) + + assert.equal(testSpan.intrinsics.parentId, null) + assert.ok(testSpan.intrinsics['nr.entryPoint']) + assert.ok(testSpan.intrinsics['nr.entryPoint']) + assert.ok(testSpan.intrinsics.category) + assert.equal(testSpan.intrinsics.category, 'generic') + assert.ok(testSpan.intrinsics.priority) + assert.equal(testSpan.intrinsics.priority, transaction.priority) + assert.ok(testSpan.intrinsics.transactionId) + assert.equal(testSpan.intrinsics.transactionId, transaction.id) + assert.ok(testSpan.intrinsics.sampled) + assert.equal(testSpan.intrinsics.sampled, transaction.sampled) + assert.ok(testSpan.intrinsics.name) + assert.equal(testSpan.intrinsics.name, 'test') + assert.ok(testSpan.intrinsics.traceId) + assert.equal(testSpan.intrinsics.traceId, transaction.traceId) + assert.ok(testSpan.intrinsics.timestamp) }) - t.test('should not generate span events on end if span_events is disabled', (t) => { + await t.test('should not generate span events on end if span_events is disabled', (t) => { + const { agent } = t.nr agent.config.span_events.enabled = false agent.config.distributed_tracing.enabled = true @@ -182,11 +179,11 @@ tap.test('Trace', (t) => { transaction.end() const events = agent.spanEventAggregator.getEvents() - t.equal(events.length, 0) - t.end() + assert.equal(events.length, 0) }) - t.test('should not generate span events on end if distributed_tracing is off', (t) => { + await t.test('should not generate span events on end if distributed_tracing is off', (t) => { + const { agent } = t.nr agent.config.span_events.enabled = true agent.config.distributed_tracing.enabled = false @@ -203,11 +200,11 @@ tap.test('Trace', (t) => { transaction.end() const events = agent.spanEventAggregator.getEvents() - t.equal(events.length, 0) - t.end() + assert.equal(events.length, 0) }) - t.test('should not generate span events on end if transaction is not sampled', (t) => { + await t.test('should not generate span events on end if transaction is not sampled', (t) => { + const { agent } = t.nr agent.config.span_events.enabled = true agent.config.distributed_tracing.enabled = false @@ -227,11 +224,11 @@ tap.test('Trace', (t) => { transaction.end() const events = agent.spanEventAggregator.getEvents() - t.equal(events.length, 0) - t.end() + assert.equal(events.length, 0) }) - t.test('parent.* attributes should be present on generated spans', (t) => { + await t.test('parent.* attributes should be present on generated spans', (t) => { + const { agent } = t.nr // Setup DT const encKey = 'gringletoes' agent.config.encoding_key = encKey @@ -269,29 +266,29 @@ tap.test('Trace', (t) => { // Test that a child span event has the attributes const attrs = child.attributes.get(DESTINATIONS.SPAN_EVENT) - t.same(attrs, { + assert.deepEqual(attrs, { 'parent.type': 'App', 'parent.app': 222, 'parent.account': 111, 'parent.transportType': 'HTTP', 'parent.transportDuration': 0 }) - t.end() }) - t.test('should send host display name on transaction when set by user', (t) => { + await t.test('should send host display name on transaction when set by user', (t) => { + const { agent } = t.nr agent.config.attributes.enabled = true agent.config.process_host.display_name = 'test-value' const trace = new Trace(new Transaction(agent)) - t.same(trace.attributes.get(DESTINATIONS.TRANS_TRACE), { + assert.deepEqual(trace.attributes.get(DESTINATIONS.TRANS_TRACE), { 'host.displayName': 'test-value' }) - t.end() }) - t.test('should send host display name attribute on span', (t) => { + await t.test('should send host display name attribute on span', (t) => { + const { agent } = t.nr agent.config.attributes.enabled = true agent.config.distributed_tracing.enabled = true agent.config.process_host.display_name = 'test-value' @@ -307,72 +304,77 @@ tap.test('Trace', (t) => { trace.generateSpanEvents() - t.same(child.attributes.get(DESTINATIONS.SPAN_EVENT), { + assert.deepEqual(child.attributes.get(DESTINATIONS.SPAN_EVENT), { 'host.displayName': 'test-value' }) - t.end() }) - t.test('should not send host display name when not set by user', (t) => { + await t.test('should not send host display name when not set by user', (t) => { + const { agent } = t.nr const trace = new Trace(new Transaction(agent)) - t.same(trace.attributes.get(DESTINATIONS.TRANS_TRACE), {}) - t.end() + assert.deepEqual(trace.attributes.get(DESTINATIONS.TRANS_TRACE), {}) }) }) -tap.test('when serializing synchronously', (t) => { - t.autoend() - let details - - let agent = null - - t.beforeEach(async () => { - agent = helper.loadMockedAgent() - details = await makeTrace(t, agent) +test('when serializing synchronously', async (t) => { + t.beforeEach(async (ctx) => { + const agent = helper.loadMockedAgent() + const details = await makeTrace(agent) + ctx.nr = { + agent, + details + } }) - t.afterEach(() => { - helper.unloadAgent(agent) + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - t.test('should produce a transaction trace in the expected format', async (t) => { + await t.test('should produce a transaction trace in the expected format', async (t) => { + const { details } = t.nr const traceJSON = details.trace.generateJSONSync() const reconstituted = await codecDecodeAsync(traceJSON[4]) - t.same(traceJSON, details.expectedEncoding, 'full trace JSON') + assert.deepEqual(traceJSON, details.expectedEncoding, 'full trace JSON') - t.same(reconstituted, details.rootNode, 'reconstituted trace segments') - t.end() + assert.deepEqual(reconstituted, details.rootNode, 'reconstituted trace segments') }) - t.test('should send response time', (t) => { + await t.test('should send response time', (t) => { + const { details } = t.nr details.transaction.getResponseTimeInMillis = () => { return 1234 } const json = details.trace.generateJSONSync() - t.equal(json[1], 1234) - t.end() + assert.equal(json[1], 1234) }) - t.test('when `simple_compression` is `false`, should compress the segment arrays', async (t) => { - const json = details.trace.generateJSONSync() + await t.test( + 'when `simple_compression` is `false`, should compress the segment arrays', + async (t) => { + const { details } = t.nr + const json = details.trace.generateJSONSync() - t.match(json[4], /^[a-zA-Z0-9\+\/]+={0,2}$/, 'should be base64 encoded') + assert.match(json[4], /^[a-zA-Z0-9\+\/]+={0,2}$/, 'should be base64 encoded') - const data = await codecDecodeAsync(json[4]) - t.same(data, details.rootNode) - t.end() - }) + const data = await codecDecodeAsync(json[4]) + assert.deepEqual(data, details.rootNode) + } + ) - t.test('when `simple_compression` is `true`, should not compress the segment arrays', (t) => { - agent.config.simple_compression = true - const json = details.trace.generateJSONSync() - t.same(json[4], details.rootNode) - t.end() - }) + await t.test( + 'when `simple_compression` is `true`, should not compress the segment arrays', + (t) => { + const { agent, details } = t.nr + agent.config.simple_compression = true + const json = details.trace.generateJSONSync() + assert.deepEqual(json[4], details.rootNode) + } + ) - t.test('when url_obfuscation is set, should obfuscate the URL', (t) => { + await t.test('when url_obfuscation is set, should obfuscate the URL', (t) => { + const { agent, details } = t.nr agent.config.url_obfuscation = { enabled: true, regex: { @@ -382,38 +384,36 @@ tap.test('when serializing synchronously', (t) => { } const json = details.trace.generateJSONSync() - t.equal(json[3], '/***') - t.end() + assert.equal(json[3], '/***') }) }) -tap.test('when serializing asynchronously', (t) => { - t.autoend() - - let details - - let agent = null - - t.beforeEach(async () => { - agent = helper.loadMockedAgent() - details = await makeTrace(t, agent) +test('when serializing asynchronously', async (t) => { + t.beforeEach(async (ctx) => { + const agent = helper.loadMockedAgent() + const details = await makeTrace(agent) + ctx.nr = { + agent, + details + } }) - t.afterEach(() => { - helper.unloadAgent(agent) + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - t.test('should produce a transaction trace in the expected format', async (t) => { + await t.test('should produce a transaction trace in the expected format', async (t) => { + const { details } = t.nr const traceJSON = await details.trace.generateJSONAsync() const reconstituted = await codecDecodeAsync(traceJSON[4]) - t.same(traceJSON, details.expectedEncoding, 'full trace JSON') + assert.deepEqual(traceJSON, details.expectedEncoding, 'full trace JSON') - t.same(reconstituted, details.rootNode, 'reconstituted trace segments') - t.end() + assert.deepEqual(reconstituted, details.rootNode, 'reconstituted trace segments') }) - t.test('should send response time', (t) => { + await t.test('should send response time', async (t) => { + const { details } = t.nr details.transaction.getResponseTimeInMillis = () => { return 1234 } @@ -427,75 +427,80 @@ tap.test('when serializing asynchronously', (t) => { reject(err) } - t.equal(json[1], 1234) - t.equal(trace, details.trace) + assert.equal(json[1], 1234) + assert.equal(trace, details.trace) resolve() }) }) }) - t.test('when `simple_compression` is `false`, should compress the segment arrays', async (t) => { - const json = await details.trace.generateJSONAsync() - t.match(json[4], /^[a-zA-Z0-9\+\/]+={0,2}$/, 'should be base64 encoded') + await t.test( + 'when `simple_compression` is `false`, should compress the segment arrays', + async (t) => { + const { details } = t.nr + const json = await details.trace.generateJSONAsync() + assert.match(json[4], /^[a-zA-Z0-9\+\/]+={0,2}$/, 'should be base64 encoded') - const data = await codecDecodeAsync(json[4]) - t.same(data, details.rootNode) - t.end() - }) + const data = await codecDecodeAsync(json[4]) + assert.deepEqual(data, details.rootNode) + } + ) - t.test( + await t.test( 'when `simple_compression` is `true`, should not compress the segment arrays', async (t) => { + const { agent, details } = t.nr agent.config.simple_compression = true const json = await details.trace.generateJSONAsync() - t.same(json[4], details.rootNode) - t.end() + assert.deepEqual(json[4], details.rootNode) } ) }) -tap.test('when inserting segments', (t) => { - t.autoend() - let agent - let trace = null - let transaction = null - - t.beforeEach(() => { - agent = helper.loadMockedAgent() - transaction = new Transaction(agent) - trace = transaction.trace +test('when inserting segments', async (t) => { + t.beforeEach((ctx) => { + const agent = helper.loadMockedAgent() + const transaction = new Transaction(agent) + const trace = transaction.trace + ctx.nr = { + agent, + trace, + transaction + } }) - t.afterEach(() => { - helper.unloadAgent(agent) + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - t.test('should allow child segments on a trace', (t) => { - t.doesNotThrow(() => { + await t.test('should allow child segments on a trace', (t) => { + const { trace } = t.nr + assert.doesNotThrow(() => { trace.add('Custom/Test17/Child1') }) - t.end() }) - t.test('should return the segment', (t) => { + await t.test('should return the segment', (t) => { + const { trace } = t.nr let segment - t.doesNotThrow(() => { + assert.doesNotThrow(() => { segment = trace.add('Custom/Test18/Child1') }) - t.type(segment, Segment) - t.end() + assert.ok(segment instanceof Segment) }) - t.test('should call a function associated with the segment', (t) => { + await t.test('should call a function associated with the segment', (t, end) => { + const { trace, transaction } = t.nr const segment = trace.add('Custom/Test18/Child1', () => { - t.end() + end() }) segment.end() transaction.end() }) - t.test('should report total time', (t) => { + await t.test('should report total time', (t) => { + const { trace } = t.nr trace.setDurationInMillis(40, 0) const child = trace.add('Custom/Test18/Child1') child.setDurationInMillis(27, 0) @@ -507,11 +512,11 @@ tap.test('when inserting segments', (t) => { seg.setDurationInMillis(9, 16) seg = child.add('UnitTest2') seg.setDurationInMillis(14, 16) - t.equal(trace.getTotalTimeDurationInMillis(), 48) - t.end() + assert.equal(trace.getTotalTimeDurationInMillis(), 48) }) - t.test('should report total time on branched traces', (t) => { + await t.test('should report total time on branched traces', (t) => { + const { trace } = t.nr trace.setDurationInMillis(40, 0) const child = trace.add('Custom/Test18/Child1') child.setDurationInMillis(27, 0) @@ -523,11 +528,11 @@ tap.test('when inserting segments', (t) => { seg.setDurationInMillis(9, 16) seg = seg1.add('UnitTest2') seg.setDurationInMillis(14, 16) - t.equal(trace.getTotalTimeDurationInMillis(), 48) - t.end() + assert.equal(trace.getTotalTimeDurationInMillis(), 48) }) - t.test('should report the expected trees for trees with uncollected segments', (t) => { + await t.test('should report the expected trees for trees with uncollected segments', (t) => { + const { trace } = t.nr const expectedTrace = [ 0, 27, @@ -573,11 +578,11 @@ tap.test('when inserting segments', (t) => { trace.end() - t.same(child.toJSON(), expectedTrace) - t.end() + assert.deepEqual(child.toJSON(), expectedTrace) }) - t.test('should report the expected trees for branched trees', (t) => { + await t.test('should report the expected trees for branched trees', (t) => { + const { trace } = t.nr const expectedTrace = [ 0, 27, @@ -624,21 +629,21 @@ tap.test('when inserting segments', (t) => { trace.end() - t.same(child.toJSON(), expectedTrace) - t.end() + assert.deepEqual(child.toJSON(), expectedTrace) }) - t.test('should measure exclusive time vs total time at each level of the graph', (t) => { + await t.test('should measure exclusive time vs total time at each level of the graph', (t) => { + const { trace } = t.nr const child = trace.add('Custom/Test18/Child1') trace.setDurationInMillis(42) child.setDurationInMillis(22, 0) - t.equal(trace.getExclusiveDurationInMillis(), 20) - t.end() + assert.equal(trace.getExclusiveDurationInMillis(), 20) }) - t.test('should accurately sum overlapping segments', (t) => { + await t.test('should accurately sum overlapping segments', (t) => { + const { trace } = t.nr trace.setDurationInMillis(42) const now = Date.now() @@ -658,11 +663,11 @@ tap.test('when inserting segments', (t) => { const child4 = trace.add('Custom/Test19/Child4') child4.setDurationInMillis(4, now + 35) - t.equal(trace.getExclusiveDurationInMillis(), 5) - t.end() + assert.equal(trace.getExclusiveDurationInMillis(), 5) }) - t.test('should accurately sum overlapping subtrees', (t) => { + await t.test('should accurately sum overlapping subtrees', (t) => { + const { trace } = t.nr trace.setDurationInMillis(42) const now = Date.now() @@ -682,15 +687,15 @@ tap.test('when inserting segments', (t) => { const child4 = child2.add('Custom/Test20/Child3') child4.setDurationInMillis(11, now + 16) - t.equal(trace.getExclusiveDurationInMillis(), 9) - t.equal(child4.getExclusiveDurationInMillis(), 11) - t.equal(child3.getExclusiveDurationInMillis(), 11) - t.equal(child2.getExclusiveDurationInMillis(), 0) - t.equal(child1.getExclusiveDurationInMillis(), 11) - t.end() + assert.equal(trace.getExclusiveDurationInMillis(), 9) + assert.equal(child4.getExclusiveDurationInMillis(), 11) + assert.equal(child3.getExclusiveDurationInMillis(), 11) + assert.equal(child2.getExclusiveDurationInMillis(), 0) + assert.equal(child1.getExclusiveDurationInMillis(), 11) }) - t.test('should accurately sum partially overlapping segments', (t) => { + await t.test('should accurately sum partially overlapping segments', (t) => { + const { trace } = t.nr trace.setDurationInMillis(42) const now = Date.now() @@ -708,11 +713,11 @@ tap.test('when inserting segments', (t) => { const child3 = trace.add('Custom/Test20/Child3') child3.setDurationInMillis(33, now) - t.equal(trace.getExclusiveDurationInMillis(), 9) - t.end() + assert.equal(trace.getExclusiveDurationInMillis(), 9) }) - t.test('should accurately sum partially overlapping, open-ranged segments', (t) => { + await t.test('should accurately sum partially overlapping, open-ranged segments', (t) => { + const { trace } = t.nr trace.setDurationInMillis(42) const now = Date.now() @@ -724,33 +729,32 @@ tap.test('when inserting segments', (t) => { const child2 = trace.add('Custom/Test21/Child2') child2.setDurationInMillis(11, now + 22) - t.equal(trace.getExclusiveDurationInMillis(), 9) - t.end() + assert.equal(trace.getExclusiveDurationInMillis(), 9) }) - t.test('should be limited to 900 children', (t) => { + await t.test('should be limited to 900 children', (t) => { + const { trace, transaction } = t.nr // They will be tagged as _collect = false after the limit runs out. for (let i = 0; i < 950; ++i) { const segment = trace.add(i.toString(), noop) if (i < 900) { - t.equal(segment._collect, true, `segment ${i} should be collected`) + assert.equal(segment._collect, true, `segment ${i} should be collected`) } else { - t.equal(segment._collect, false, `segment ${i} should not be collected`) + assert.equal(segment._collect, false, `segment ${i} should not be collected`) } } - t.equal(trace.root.children.length, 950) - t.equal(transaction._recorders.length, 950) + assert.equal(trace.root.children.length, 950) + assert.equal(transaction._recorders.length, 950) trace.segmentCount = 0 trace.root.children = [] trace.recorders = [] function noop() {} - t.end() }) }) -tap.test('should set URI to null when request.uri attribute is excluded globally', async (t) => { +test('should set URI to null when request.uri attribute is excluded globally', async (t) => { const URL = '/test' const agent = helper.loadMockedAgent({ @@ -759,7 +763,7 @@ tap.test('should set URI to null when request.uri attribute is excluded globally } }) - t.teardown(() => { + t.after(() => { helper.unloadAgent(agent) }) @@ -774,11 +778,10 @@ tap.test('should set URI to null when request.uri attribute is excluded globally const traceJSON = await trace.generateJSON() const { 3: requestUri } = traceJSON - t.notOk(requestUri) - t.end() + assert.ok(!requestUri) }) -tap.test('should set URI to null when request.uri attribute is exluded from traces', async (t) => { +test('should set URI to null when request.uri attribute is exluded from traces', async (t) => { const URL = '/test' const agent = helper.loadMockedAgent({ @@ -789,7 +792,7 @@ tap.test('should set URI to null when request.uri attribute is exluded from trac } }) - t.teardown(() => { + t.after(() => { helper.unloadAgent(agent) }) @@ -804,14 +807,13 @@ tap.test('should set URI to null when request.uri attribute is exluded from trac const traceJSON = await trace.generateJSON() const { 3: requestUri } = traceJSON - t.notOk(requestUri) - t.end() + assert.ok(!requestUri) }) -tap.test('should set URI to /Unknown when URL is not known/set on transaction', async (t) => { +test('should set URI to /Unknown when URL is not known/set on transaction', async (t) => { const agent = helper.loadMockedAgent() - t.teardown(() => { + t.after(() => { helper.unloadAgent(agent) }) @@ -823,11 +825,10 @@ tap.test('should set URI to /Unknown when URL is not known/set on transaction', const traceJSON = await trace.generateJSON() const { 3: requestUri } = traceJSON - t.equal(requestUri, '/Unknown') - t.end() + assert.equal(requestUri, '/Unknown') }) -tap.test('should obfuscate URI using regex when pattern is set', async (t) => { +test('should obfuscate URI using regex when pattern is set', async (t) => { const URL = '/abc/123/def/456/ghi' const agent = helper.loadMockedAgent({ url_obfuscation: { @@ -840,7 +841,7 @@ tap.test('should obfuscate URI using regex when pattern is set', async (t) => { } }) - t.teardown(() => { + t.after(() => { helper.unloadAgent(agent) }) @@ -855,11 +856,83 @@ tap.test('should obfuscate URI using regex when pattern is set', async (t) => { const traceJSON = await trace.generateJSON() const { 3: requestUri } = traceJSON - t.equal(requestUri, '/abc/***/def/***/ghi') - t.end() + assert.equal(requestUri, '/abc/***/def/***/ghi') +}) + +test('infinite tracing', async (t) => { + const VALID_HOST = 'infinite-tracing.test' + const VALID_PORT = '443' + + t.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.loadMockedAgent({ + distributed_tracing: { + enabled: true + }, + span_events: { + enabled: true + }, + infinite_tracing: { + trace_observer: { + host: VALID_HOST, + port: VALID_PORT + } + } + }) + }) + + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + }) + + await t.test('should generate spans if infinite configured, transaction not sampled', (t) => { + const { agent } = t.nr + const spy = sinon.spy(agent.spanEventAggregator, 'addSegment') + + const transaction = new Transaction(agent) + transaction.priority = 0 + transaction.sampled = false + + addTwoSegments(transaction) + + transaction.trace.generateSpanEvents() + + assert.equal(spy.callCount, 2) + }) + + await t.test( + 'should not generate spans if infinite not configured, transaction not sampled', + (t) => { + const { agent } = t.nr + agent.config.infinite_tracing.trace_observer.host = '' + + const spy = sinon.spy(agent.spanEventAggregator, 'addSegment') + + const transaction = new Transaction(agent) + transaction.priority = 0 + transaction.sampled = false + + addTwoSegments(transaction) + + transaction.trace.generateSpanEvents() + + assert.equal(spy.callCount, 0) + } + ) }) -async function makeTrace(t, agent) { +function addTwoSegments(transaction) { + const trace = transaction.trace + const child1 = (transaction.baseSegment = trace.add('test')) + child1.start() + const child2 = child1.add('nested') + child2.start() + child1.end() + child2.end() + trace.root.end() +} + +async function makeTrace(agent) { const DURATION = 33 const URL = '/test?test=value' agent.config.attributes.enabled = true @@ -879,7 +952,7 @@ async function makeTrace(t, agent) { // and instead use async/await trace.generateJSONAsync = util.promisify(trace.generateJSON) const start = trace.root.timer.start - t.ok(start > 0, "root segment's start time") + assert.ok(start > 0, "root segment's start time") trace.setDurationInMillis(DURATION, 0) const web = trace.root.add(URL) @@ -961,78 +1034,3 @@ async function makeTrace(t, agent) { ] } } - -tap.test('infinite tracing', (t) => { - t.autoend() - - const VALID_HOST = 'infinite-tracing.test' - const VALID_PORT = '443' - - let agent = null - - t.beforeEach(() => { - agent = helper.loadMockedAgent({ - distributed_tracing: { - enabled: true - }, - span_events: { - enabled: true - }, - infinite_tracing: { - trace_observer: { - host: VALID_HOST, - port: VALID_PORT - } - } - }) - }) - - t.afterEach(() => { - helper.unloadAgent(agent) - }) - - t.test('should generate spans if infinite configured, transaction not sampled', (t) => { - const spy = sinon.spy(agent.spanEventAggregator, 'addSegment') - - const transaction = new Transaction(agent) - transaction.priority = 0 - transaction.sampled = false - - addTwoSegments(transaction) - - transaction.trace.generateSpanEvents() - - t.equal(spy.callCount, 2) - - t.end() - }) - - t.test('should not generate spans if infinite not configured, transaction not sampled', (t) => { - agent.config.infinite_tracing.trace_observer.host = '' - - const spy = sinon.spy(agent.spanEventAggregator, 'addSegment') - - const transaction = new Transaction(agent) - transaction.priority = 0 - transaction.sampled = false - - addTwoSegments(transaction) - - transaction.trace.generateSpanEvents() - - t.equal(spy.callCount, 0) - - t.end() - }) -}) - -function addTwoSegments(transaction) { - const trace = transaction.trace - const child1 = (transaction.baseSegment = trace.add('test')) - child1.start() - const child2 = child1.add('nested') - child2.start() - child1.end() - child2.end() - trace.root.end() -} diff --git a/test/unit/transaction/trace/segment.test.js b/test/unit/transaction/trace/segment.test.js new file mode 100644 index 0000000000..a56a4444a6 --- /dev/null +++ b/test/unit/transaction/trace/segment.test.js @@ -0,0 +1,627 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* eslint dot-notation: off */ +'use strict' +const assert = require('node:assert') +const test = require('node:test') +const { DESTINATIONS } = require('../../../../lib/config/attribute-filter') +const sinon = require('sinon') +const helper = require('../../../lib/agent_helper') +const TraceSegment = require('../../../../lib/transaction/trace/segment') +const Transaction = require('../../../../lib/transaction') + +function beforeEach(ctx) { + ctx.nr = {} + ctx.nr.agent = helper.loadMockedAgent() +} + +function afterEach(ctx) { + helper.unloadAgent(ctx.nr.agent) +} + +test('TraceSegment', async (t) => { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await t.test('should be bound to a Trace', (t) => { + const { agent } = t.nr + let segment = null + const trans = new Transaction(agent) + assert.throws(function noTrace() { + segment = new TraceSegment(null, 'UnitTest') + }) + assert.equal(segment, null) + + const success = new TraceSegment(trans, 'UnitTest') + assert.equal(success.transaction, trans) + trans.end() + }) + + await t.test('should not add new children when marked as opaque', (t) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const segment = new TraceSegment(trans, 'UnitTest') + assert.ok(!segment.opaque) + segment.opaque = true + segment.add('child') + assert.equal(segment.children.length, 0) + segment.opaque = false + segment.add('child') + assert.equal(segment.children.length, 1) + trans.end() + }) + + await t.test('should call an optional callback function', (t, end) => { + const { agent } = t.nr + const trans = new Transaction(agent) + assert.doesNotThrow(function noCallback() { + new TraceSegment(trans, 'UnitTest') // eslint-disable-line no-new + }) + const working = new TraceSegment(trans, 'UnitTest', function () { + end() + }) + working.end() + trans.end() + }) + + await t.test('has a name', (t) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const success = new TraceSegment(trans, 'UnitTest') + assert.equal(success.name, 'UnitTest') + }) + + await t.test('is created with no children', (t) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const segment = new TraceSegment(trans, 'UnitTest') + assert.equal(segment.children.length, 0) + }) + + await t.test('has a timer', (t) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const segment = new TraceSegment(trans, 'UnitTest') + assert.ok(segment.timer) + }) + + await t.test('does not start its timer on creation', (t) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const segment = new TraceSegment(trans, 'UnitTest') + assert.equal(segment.timer.isRunning(), false) + }) + + await t.test('allows the timer to be updated without ending it', (t) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const segment = new TraceSegment(trans, 'UnitTest') + segment.start() + segment.touch() + assert.equal(segment.timer.isRunning(), true) + assert.ok(segment.getDurationInMillis() > 0) + }) + + await t.test('accepts a callback that records metrics for this segment', (t, end) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const segment = new TraceSegment(trans, 'Test', (insider) => { + assert.equal(insider, segment) + end() + }) + segment.end() + trans.end() + }) + + await t.test('should return the segment id when dt and spans are enabled', (t) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const segment = new TraceSegment(trans, 'Test') + agent.config.distributed_tracing.enabled = true + agent.config.span_events.enabled = true + assert.equal(segment.getSpanId(), segment.id) + }) + + await t.test('should return null when dt is disabled', (t) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const segment = new TraceSegment(trans, 'Test') + agent.config.distributed_tracing.enabled = false + agent.config.span_events.enabled = true + assert.equal(segment.getSpanId(), null) + }) + + await t.test('should return null when spans are disabled', (t) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const segment = new TraceSegment(trans, 'Test') + agent.config.distributed_tracing.enabled = true + agent.config.span_events.enabled = false + assert.ok(segment.getSpanId() === null) + }) + + await t.test('updates root segment timer when end() is called', (t, end) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const trace = trans.trace + const segment = new TraceSegment(trans, 'Test') + + segment.setDurationInMillis(10, 0) + + setTimeout(() => { + assert.equal(trace.root.timer.hrDuration, null) + segment.end() + assert.ok(trace.root.timer.getDurationInMillis() > segment.timer.getDurationInMillis() - 1) // alow for slop + end() + }, 10) + }) + + await t.test('properly tracks the number of active or harvested segments', (t, end) => { + const { agent } = t.nr + assert.equal(agent.activeTransactions, 0) + assert.equal(agent.totalActiveSegments, 0) + assert.equal(agent.segmentsCreatedInHarvest, 0) + + const tx = new Transaction(agent) + assert.equal(agent.totalActiveSegments, 1) + assert.equal(agent.segmentsCreatedInHarvest, 1) + assert.equal(tx.numSegments, 1) + assert.equal(agent.activeTransactions, 1) + + const segment = new TraceSegment(tx, 'Test') // eslint-disable-line no-unused-vars + assert.equal(agent.totalActiveSegments, 2) + assert.equal(agent.segmentsCreatedInHarvest, 2) + assert.equal(tx.numSegments, 2) + tx.end() + + assert.equal(agent.activeTransactions, 0) + + setTimeout(function () { + assert.equal(agent.totalActiveSegments, 0) + assert.equal(agent.segmentsClearedInHarvest, 2) + + agent.forceHarvestAll(() => { + assert.equal(agent.totalActiveSegments, 0) + assert.equal(agent.segmentsClearedInHarvest, 0) + assert.equal(agent.segmentsCreatedInHarvest, 0) + end() + }) + }, 10) + }) + + await t.test('toJSON should not modify attributes', (t) => { + const { agent } = t.nr + const transaction = new Transaction(agent) + const segment = new TraceSegment(transaction, 'TestSegment') + segment.toJSON() + assert.deepEqual(segment.getAttributes(), {}) + }) + + await t.test('when ended stops its timer', (t) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const segment = new TraceSegment(trans, 'UnitTest') + segment.end() + assert.equal(segment.timer.isRunning(), false) + }) + + await t.test('should produce JSON that conforms to the collector spec', (t) => { + const { agent } = t.nr + const transaction = new Transaction(agent) + const trace = transaction.trace + const segment = trace.add('DB/select/getSome') + + trace.setDurationInMillis(17, 0) + segment.setDurationInMillis(14, 3) + + trace.end() + + // See documentation on TraceSegment.toJSON for what goes in which field. + assert.deepEqual(segment.toJSON(), [ + 3, + 17, + 'DB/select/getSome', + { nr_exclusive_duration_millis: 14 }, + [] + ]) + }) + + await t.test('#finalize should add nr_exclusive_duration_millis attribute', (t) => { + const { agent } = t.nr + const transaction = new Transaction(agent) + const segment = new TraceSegment(transaction, 'TestSegment') + + segment._setExclusiveDurationInMillis(1) + + assert.deepEqual(segment.getAttributes(), {}) + + segment.finalize() + + assert.equal(segment.getAttributes()['nr_exclusive_duration_millis'], 1) + }) + + await t.test('should truncate when timer still running', (t) => { + const { agent } = t.nr + const segmentName = 'TestSegment' + + const transaction = new Transaction(agent) + const segment = new TraceSegment(transaction, segmentName) + + // Force truncation + sinon.stub(segment.timer, 'softEnd').returns(true) + sinon.stub(segment.timer, 'endsAfter').returns(true) + + const root = transaction.trace.root + + // Make root duration calculation predictable + root.timer.start = 1000 + segment.timer.start = 1001 + segment.overwriteDurationInMillis(3) + + segment.finalize() + + assert.equal(segment.name, `Truncated/${segmentName}`) + assert.equal(root.getDurationInMillis(), 4) + }) +}) + +test('with children created from URLs', async (t) => { + t.beforeEach((ctx) => { + beforeEach(ctx) + ctx.nr.agent.config.attributes.enabled = true + ctx.nr.agent.config.attributes.include.push('request.parameters.*') + ctx.nr.agent.config.emit('attributes.include') + + const transaction = new Transaction(ctx.nr.agent) + const trace = transaction.trace + const segment = trace.add('UnitTest') + + const url = '/test?test1=value1&test2&test3=50&test4=' + + const webChild = segment.add(url) + transaction.baseSegment = webChild + transaction.finalizeNameFromUri(url, 200) + + trace.setDurationInMillis(1, 0) + webChild.setDurationInMillis(1, 0) + + trace.end() + ctx.nr.webChild = webChild + }) + + t.afterEach(afterEach) + + await t.test('should return the URL minus any query parameters', (t) => { + const { webChild } = t.nr + assert.equal(webChild.name, 'WebTransaction/NormalizedUri/*') + }) + + await t.test('should have attributes on the child segment', (t) => { + const { webChild } = t.nr + assert.ok(webChild.getAttributes()) + }) + + await t.test('should have the parameters that were passed in the query string', (t) => { + const { webChild } = t.nr + const attributes = webChild.getAttributes() + assert.equal(attributes['request.parameters.test1'], 'value1') + assert.equal(attributes['request.parameters.test3'], '50') + }) + + await t.test('should set bare parameters to true (as in present)', (t) => { + const { webChild } = t.nr + assert.equal(webChild.getAttributes()['request.parameters.test2'], true) + }) + + await t.test('should set parameters with empty values to ""', (t) => { + const { webChild } = t.nr + assert.equal(webChild.getAttributes()['request.parameters.test4'], '') + }) + + await t.test('should serialize the segment with the parameters', (t) => { + const { webChild } = t.nr + assert.deepEqual(webChild.toJSON(), [ + 0, + 1, + 'WebTransaction/NormalizedUri/*', + { + 'nr_exclusive_duration_millis': 1, + 'request.parameters.test1': 'value1', + 'request.parameters.test2': true, + 'request.parameters.test3': '50', + 'request.parameters.test4': '' + }, + [] + ]) + }) +}) + +test('with parameters parsed out by framework', async (t) => { + t.beforeEach((ctx) => { + beforeEach(ctx) + ctx.nr.agent.config.attributes.enabled = true + + const transaction = new Transaction(ctx.nr.agent) + const trace = transaction.trace + trace.mer = 6 + + const segment = trace.add('UnitTest') + + const url = '/test' + const params = {} + + // Express uses positional parameters sometimes + params[0] = 'first' + params[1] = 'another' + params.test3 = '50' + + const webChild = segment.add(url) + transaction.trace.attributes.addAttributes(DESTINATIONS.TRANS_SCOPE, params) + transaction.baseSegment = webChild + transaction.finalizeNameFromUri(url, 200) + + trace.setDurationInMillis(1, 0) + webChild.setDurationInMillis(1, 0) + + trace.end() + ctx.nr.webChild = webChild + ctx.nr.trace = trace + }) + t.afterEach(afterEach) + + await t.test('should return the URL minus any query parameters', (t) => { + const { webChild } = t.nr + assert.equal(webChild.name, 'WebTransaction/NormalizedUri/*') + }) + + await t.test('should have attributes on the trace', (t) => { + const { trace } = t.nr + assert.ok(trace.attributes.get(DESTINATIONS.TRANS_TRACE)) + }) + + await t.test('should have the positional parameters from the params array', (t) => { + const { trace } = t.nr + const attributes = trace.attributes.get(DESTINATIONS.TRANS_TRACE) + assert.equal(attributes[0], 'first') + assert.equal(attributes[1], 'another') + }) + + await t.test('should have the named parameter from the params array', (t) => { + const { trace } = t.nr + assert.equal(trace.attributes.get(DESTINATIONS.TRANS_TRACE)['test3'], '50') + }) + + await t.test('should serialize the segment with the parameters', (t) => { + const { webChild } = t.nr + const expected = [ + 0, + 1, + 'WebTransaction/NormalizedUri/*', + { + nr_exclusive_duration_millis: 1, + 0: 'first', + 1: 'another', + test3: '50' + }, + [] + ] + assert.deepEqual(webChild.toJSON(), expected) + }) +}) + +test('with attributes.enabled set to false', async (t) => { + t.beforeEach((ctx) => { + beforeEach(ctx) + ctx.nr.agent.config.attributes.enabled = false + + const transaction = new Transaction(ctx.nr.agent) + const trace = transaction.trace + const segment = new TraceSegment(transaction, 'UnitTest') + const url = '/test?test1=value1&test2&test3=50&test4=' + + const webChild = segment.add(url) + webChild.addAttribute('test', 'non-null value') + transaction.baseSegment = webChild + transaction.finalizeNameFromUri(url, 200) + + trace.setDurationInMillis(1, 0) + webChild.setDurationInMillis(1, 0) + ctx.nr.webChild = webChild + }) + t.afterEach(afterEach) + + await t.test('should return the URL minus any query parameters', (t) => { + const { webChild } = t.nr + assert.equal(webChild.name, 'WebTransaction/NormalizedUri/*') + }) + + await t.test('should have no attributes on the child segment', (t) => { + const { webChild } = t.nr + assert.deepEqual(webChild.getAttributes(), {}) + }) + + await t.test('should serialize the segment without the parameters', (t) => { + const { webChild } = t.nr + const expected = [0, 1, 'WebTransaction/NormalizedUri/*', {}, []] + assert.deepEqual(webChild.toJSON(), expected) + }) +}) + +test('with attributes.enabled set', async (t) => { + t.beforeEach((ctx) => { + beforeEach(ctx) + ctx.nr.agent.config.attributes.enabled = true + ctx.nr.agent.config.attributes.include = ['request.parameters.*'] + ctx.nr.agent.config.attributes.exclude = [ + 'request.parameters.test1', + 'request.parameters.test4' + ] + ctx.nr.agent.config.emit('attributes.exclude') + + const transaction = new Transaction(ctx.nr.agent) + const trace = transaction.trace + const segment = trace.add('UnitTest') + + const url = '/test?test1=value1&test2&test3=50&test4=' + + const webChild = segment.add(url) + transaction.baseSegment = webChild + transaction.finalizeNameFromUri(url, 200) + webChild.markAsWeb(url) + + trace.setDurationInMillis(1, 0) + webChild.setDurationInMillis(1, 0) + ctx.nr.attributes = webChild.getAttributes() + ctx.nr.webChild = webChild + + trace.end() + }) + t.afterEach(afterEach) + + await t.test('should return the URL minus any query parameters', (t) => { + const { webChild } = t.nr + assert.equal(webChild.name, 'WebTransaction/NormalizedUri/*') + }) + + await t.test('should have attributes on the child segment', (t) => { + const { attributes } = t.nr + assert.ok(attributes) + }) + + await t.test('should filter the parameters that were passed in the query string', (t) => { + const { attributes } = t.nr + assert.equal(attributes['test1'], undefined) + assert.equal(attributes['request.parameters.test1'], undefined) + + assert.equal(attributes['test3'], undefined) + assert.equal(attributes['request.parameters.test3'], '50') + + assert.equal(attributes['test4'], undefined) + assert.equal(attributes['request.parameters.test4'], undefined) + }) + + await t.test('should set bare parameters to true (as in present)', (t) => { + const { attributes } = t.nr + assert.equal(attributes['test2'], undefined) + assert.equal(attributes['request.parameters.test2'], true) + }) + + await t.test('should serialize the segment with the parameters', (t) => { + const { webChild } = t.nr + assert.deepEqual(webChild.toJSON(), [ + 0, + 1, + 'WebTransaction/NormalizedUri/*', + { + 'nr_exclusive_duration_millis': 1, + 'request.parameters.test2': true, + 'request.parameters.test3': '50' + }, + [] + ]) + }) +}) + +test('when serialized', async (t) => { + t.beforeEach((ctx) => { + const agent = helper.loadMockedAgent() + const trans = new Transaction(agent) + const segment = new TraceSegment(trans, 'UnitTest') + ctx.nr = { + agent, + segment, + trans + } + }) + + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + }) + + await t.test('should create a plain JS array', (t) => { + const { segment } = t.nr + segment.end() + const js = segment.toJSON() + + assert.ok(Array.isArray(js)) + assert.equal(typeof js[0], 'number') + assert.equal(typeof js[1], 'number') + + assert.equal(js[2], 'UnitTest') + + assert.equal(typeof js[3], 'object') + + assert.ok(Array.isArray(js[4])) + assert.equal(js[4].length, 0) + }) + + await t.test('should not cause a stack overflow', { timeout: 30000 }, (t) => { + const { segment, trans } = t.nr + let parent = segment + for (let i = 0; i < 9000; ++i) { + const child = new TraceSegment(trans, 'Child ' + i) + parent.children.push(child) + parent = child + } + + assert.doesNotThrow(function () { + segment.toJSON() + }) + }) +}) + +test('getSpanContext', async (t) => { + t.beforeEach((ctx) => { + const agent = helper.loadMockedAgent({ + distributed_tracing: { + enabled: true + } + }) + const transaction = new Transaction(agent) + const segment = new TraceSegment(transaction, 'UnitTest') + ctx.nr = { + agent, + segment, + transaction + } + }) + + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + }) + + await t.test('should not initialize with a span context', (t) => { + const { segment } = t.nr + assert.ok(!segment._spanContext) + }) + + await t.test('should create a new context when empty', (t) => { + const { segment } = t.nr + const spanContext = segment.getSpanContext() + assert.ok(spanContext) + }) + + await t.test('should not create a new context when empty and DT disabled', (t) => { + const { agent, segment } = t.nr + agent.config.distributed_tracing.enabled = false + const spanContext = segment.getSpanContext() + assert.ok(!spanContext) + }) + + await t.test('should not create a new context when empty and Spans disabled', (t) => { + const { agent, segment } = t.nr + agent.config.span_events.enabled = false + const spanContext = segment.getSpanContext() + assert.ok(!spanContext) + }) + + await t.test('should return existing span context', (t) => { + const { segment } = t.nr + const originalContext = segment.getSpanContext() + const secondContext = segment.getSpanContext() + assert.equal(originalContext, secondContext) + }) +}) diff --git a/test/unit/trace-aggregator.test.js b/test/unit/transaction/trace/trace-aggregator.test.js similarity index 57% rename from test/unit/trace-aggregator.test.js rename to test/unit/transaction/trace/trace-aggregator.test.js index 522103a220..d3e9aefaaa 100644 --- a/test/unit/trace-aggregator.test.js +++ b/test/unit/transaction/trace/trace-aggregator.test.js @@ -4,11 +4,12 @@ */ 'use strict' -const tap = require('tap') -const helper = require('../lib/agent_helper') -const configurator = require('../../lib/config') -const TraceAggregator = require('../../lib/transaction/trace/aggregator') -const Transaction = require('../../lib/transaction') +const assert = require('node:assert') +const test = require('node:test') +const helper = require('../../../lib/agent_helper') +const configurator = require('../../../../lib/config') +const TraceAggregator = require('../../../../lib/transaction/trace/aggregator') +const Transaction = require('../../../../lib/transaction') function createTransaction(agent, name, duration, synth) { const transaction = new Transaction(agent) @@ -31,68 +32,63 @@ function createTransaction(agent, name, duration, synth) { return transaction.end() } -function beforeEach(t) { +function beforeEach(ctx) { + ctx.nr = {} const agent = helper.loadMockedAgent({ run_id: 1337 }) agent.collector._runLifecycle = (remote, payload, cb) => { setImmediate(cb, null, [], { return_value: [] }) } - t.context.agent = agent + ctx.nr.agent = agent } -function afterEach(t) { - helper.unloadAgent(t.context.agent) +function afterEach(ctx) { + helper.unloadAgent(ctx.nr.agent) } -tap.test('TraceAggregator', function (t) { - t.autoend() - +test('TraceAggregator', async function (t) { t.beforeEach(beforeEach) t.afterEach(afterEach) - t.test('should require a configuration at startup time', function (t) { - const { agent } = t.context - t.throws(() => new TraceAggregator()) + await t.test('should require a configuration at startup time', function (t) { + const { agent } = t.nr + assert.throws(() => new TraceAggregator()) const config = configurator.initialize({ transaction_tracer: { enabled: true } }) - t.doesNotThrow(() => new TraceAggregator({ config }, agent.collector, agent.harvester)) - t.end() + assert.doesNotThrow(() => new TraceAggregator({ config }, agent.collector, agent.harvester)) }) - t.test("shouldn't collect a trace if the tracer is disabled", function (t) { - const { agent } = t.context + await t.test("shouldn't collect a trace if the tracer is disabled", function (t) { + const { agent } = t.nr agent.config.transaction_tracer.enabled = false const tx = createTransaction(agent, '/test', 3000) agent.traces.add(tx) - t.notOk(agent.traces.trace) - t.end() + assert.ok(!agent.traces.trace) }) - t.test("shouldn't collect a trace if collect_traces is false", function (t) { - const { agent } = t.context + await t.test("shouldn't collect a trace if collect_traces is false", function (t) { + const { agent } = t.nr agent.config.collect_traces = false const tx = createTransaction(agent, '/test', 3000) agent.traces.add(tx) - t.notOk(agent.traces.trace) - t.end() + assert.ok(!agent.traces.trace) }) - t.test('should let the agent decide whether to ignore a transaction', function (t) { - const { agent } = t.context + await t.test('should let the agent decide whether to ignore a transaction', function (t) { + const { agent } = t.nr const transaction = new Transaction(agent) transaction.trace.setDurationInMillis(3000) transaction.ignore = true agent.traces.add(transaction) - t.ok(agent.traces.trace) - t.end() + assert.ok(agent.traces.trace) }) - t.test('should collect traces when the threshold is 0', function (t) { - const { agent } = t.context + await t.test('should collect traces when the threshold is 0', function (t) { + const { agent } = t.nr const config = configurator.initialize({ transaction_tracer: { transaction_threshold: 0, @@ -110,12 +106,11 @@ tap.test('TraceAggregator', function (t) { transaction.statusCode = 200 aggregator.add(transaction) - t.equal(aggregator.requestTimes['WebTransaction/Uri/test'], 0) - t.end() + assert.equal(aggregator.requestTimes['WebTransaction/Uri/test'], 0) }) - t.test('should collect traces for transactions that exceed apdex_f', function (t) { - const { agent } = t.context + await t.test('should collect traces for transactions that exceed apdex_f', function (t) { + const { agent } = t.nr const ABOVE_THRESHOLD = 29 const APDEXT = 0.007 @@ -139,41 +134,42 @@ tap.test('TraceAggregator', function (t) { transaction.statusCode = 200 aggregator.add(transaction) - t.equal(aggregator.requestTimes['WebTransaction/Uri/test'], ABOVE_THRESHOLD) - t.end() + assert.equal(aggregator.requestTimes['WebTransaction/Uri/test'], ABOVE_THRESHOLD) }) - t.test("should not collect traces for transactions that don't exceed apdex_f", function (t) { - const { agent } = t.context - const BELOW_THRESHOLD = 27 - const APDEXT = 0.007 - - const config = configurator.initialize({ - transaction_tracer: { - enabled: true, - top_n: 10 - } - }) - - const aggregator = new TraceAggregator({ config }, agent.collector, agent.harvester) - const transaction = new Transaction(agent) - - aggregator.reported = 10 // needed to override "first 5" - - // let's violating Law of Demeter! - transaction.metrics.apdexT = APDEXT - transaction.trace.setDurationInMillis(BELOW_THRESHOLD) - transaction.url = '/test' - transaction.name = 'WebTransaction/Uri/test' - transaction.statusCode = 200 - - aggregator.add(transaction) - t.equal(aggregator.requestTimes['WebTransaction/Uri/test'], undefined) - t.end() - }) + await t.test( + "should not collect traces for transactions that don't exceed apdex_f", + function (t) { + const { agent } = t.nr + const BELOW_THRESHOLD = 27 + const APDEXT = 0.007 + + const config = configurator.initialize({ + transaction_tracer: { + enabled: true, + top_n: 10 + } + }) + + const aggregator = new TraceAggregator({ config }, agent.collector, agent.harvester) + const transaction = new Transaction(agent) + + aggregator.reported = 10 // needed to override "first 5" + + // let's violating Law of Demeter! + transaction.metrics.apdexT = APDEXT + transaction.trace.setDurationInMillis(BELOW_THRESHOLD) + transaction.url = '/test' + transaction.name = 'WebTransaction/Uri/test' + transaction.statusCode = 200 + + aggregator.add(transaction) + assert.equal(aggregator.requestTimes['WebTransaction/Uri/test'], undefined) + } + ) - t.test('should collect traces that exceed explicit trace threshold', (t) => { - const { agent } = t.context + await t.test('should collect traces that exceed explicit trace threshold', (t) => { + const { agent } = t.nr const ABOVE_THRESHOLD = 29 const THRESHOLD = 0.028 @@ -189,12 +185,11 @@ tap.test('TraceAggregator', function (t) { const tx = createTransaction(agent, '/test', ABOVE_THRESHOLD) aggregator.add(tx) - t.equal(aggregator.requestTimes['WebTransaction/Uri/test'], ABOVE_THRESHOLD) - t.end() + assert.equal(aggregator.requestTimes['WebTransaction/Uri/test'], ABOVE_THRESHOLD) }) - t.test('should not collect traces that do not exceed trace threshold', (t) => { - const { agent } = t.context + await t.test('should not collect traces that do not exceed trace threshold', (t) => { + const { agent } = t.nr const BELOW_THRESHOLD = 29 const THRESHOLD = 30 @@ -209,12 +204,11 @@ tap.test('TraceAggregator', function (t) { aggregator.reported = 10 // needed to override "first 5" const tx = createTransaction(agent, '/test', BELOW_THRESHOLD) aggregator.add(tx) - t.notOk(aggregator.requestTimes['WebTransaction/Uri/test']) - t.end() + assert.ok(!aggregator.requestTimes['WebTransaction/Uri/test']) }) - t.test('should group transactions by the metric name associated with them', (t) => { - const { agent } = t.context + await t.test('should group transactions by the metric name associated with them', (t) => { + const { agent } = t.nr const config = configurator.initialize({ transaction_tracer: { enabled: true, @@ -226,12 +220,11 @@ tap.test('TraceAggregator', function (t) { const tx = createTransaction(agent, '/test', 2100) aggregator.add(tx) - t.equal(aggregator.requestTimes['WebTransaction/Uri/test'], 2100) - t.end() + assert.equal(aggregator.requestTimes['WebTransaction/Uri/test'], 2100) }) - t.test('should always report slow traces until 5 have been sent', function (t) { - const { agent } = t.context + await t.test('should always report slow traces until 5 have been sent', function (t, end) { + const { agent } = t.nr agent.config.apdex_t = 0 agent.config.run_id = 1337 agent.config.transaction_tracer.enabled = true @@ -241,9 +234,9 @@ tap.test('TraceAggregator', function (t) { // repeat! const txnCreator = (n, max, cb) => { - t.notOk(agent.traces.trace, 'trace waiting to be collected') + assert.ok(!agent.traces.trace, 'trace waiting to be collected') createTransaction(agent, `/test-${n % 3}`, 500) - t.ok(agent.traces.trace, `${n}th trace to collect`) + assert.ok(agent.traces.trace, `${n}th trace to collect`) agent.traces.once('finished_data_send-transaction_sample_data', (err) => cb(err, { idx: n, max }) ) @@ -251,17 +244,17 @@ tap.test('TraceAggregator', function (t) { } const finalCallback = (err) => { - t.error(err) + assert.ok(!err) // This 6th transaction should not be collected. - t.notOk(agent.traces.trace) + assert.ok(!agent.traces.trace) createTransaction(agent, `/test-0`, 500) - t.notOk(agent.traces.trace, '6th trace to collect') - t.end() + assert.ok(!agent.traces.trace, '6th trace to collect') + end() } // Array iteration is too difficult to slow down, so this steps through recursively txnCreator(0, maxTraces, function testCallback(err, props) { - t.error(err) + assert.ok(!err) const { idx, max } = props const nextIdx = idx + 1 if (nextIdx >= max) { @@ -271,8 +264,8 @@ tap.test('TraceAggregator', function (t) { }) }) - t.test('should reset timings after 5 harvest cycles with no slow traces', (t) => { - const { agent } = t.context + await t.test('should reset timings after 5 harvest cycles with no slow traces', (t, end) => { + const { agent } = t.nr agent.config.run_id = 1337 agent.config.transaction_tracer.enabled = true @@ -283,16 +276,16 @@ tap.test('TraceAggregator', function (t) { let remaining = 4 // 2nd-5th harvests: no serialized trace, timing still set const looper = function () { - t.equal(aggregator.requestTimes['WebTransaction/Uri/test'], 5030) + assert.equal(aggregator.requestTimes['WebTransaction/Uri/test'], 5030) aggregator.clear() remaining-- if (remaining < 1) { // 6th harvest: no serialized trace, timings reset agent.traces.once('finished_data_send-transaction_sample_data', function () { - t.notOk(aggregator.requestTimes['WebTransaction/Uri/test']) + assert.ok(!aggregator.requestTimes['WebTransaction/Uri/test']) - t.end() + end() }) agent.traces.send() } else { @@ -304,7 +297,7 @@ tap.test('TraceAggregator', function (t) { aggregator.add(tx) agent.traces.once('finished_data_send-transaction_sample_data', function () { - t.equal(aggregator.requestTimes['WebTransaction/Uri/test'], 5030) + assert.equal(aggregator.requestTimes['WebTransaction/Uri/test'], 5030) aggregator.clear() agent.traces.once('finished_data_send-transaction_sample_data', looper) @@ -313,30 +306,28 @@ tap.test('TraceAggregator', function (t) { agent.traces.send() }) - t.test('should reset the syntheticsTraces when resetting trace', function (t) { - const { agent } = t.context + await t.test('should reset the syntheticsTraces when resetting trace', function (t) { + const { agent } = t.nr agent.config.transaction_tracer.enabled = true const aggregator = agent.traces createTransaction(agent, '/testOne', 503) - t.ok(aggregator.trace) + assert.ok(aggregator.trace) aggregator.clear() createTransaction(agent, '/testTwo', 406, true) - t.notOk(aggregator.trace) - t.equal(aggregator.syntheticsTraces.length, 1) + assert.ok(!aggregator.trace) + assert.equal(aggregator.syntheticsTraces.length, 1) aggregator.clear() - t.equal(aggregator.syntheticsTraces.length, 0) - t.end() + assert.equal(aggregator.syntheticsTraces.length, 0) }) }) -tap.test('TraceAggregator with top n support', function (t) { - t.autoend() - t.beforeEach(function () { - beforeEach(t) - t.context.config = configurator.initialize({ +test('TraceAggregator with top n support', async function (t) { + t.beforeEach(function (ctx) { + beforeEach(ctx) + ctx.nr.config = configurator.initialize({ transaction_tracer: { enabled: true } @@ -345,85 +336,81 @@ tap.test('TraceAggregator with top n support', function (t) { t.afterEach(afterEach) - t.test('should set n from its configuration', function (t) { - const { config, agent } = t.context + await t.test('should set n from its configuration', function (t) { + const { config, agent } = t.nr const TOP_N = 21 config.transaction_tracer.top_n = TOP_N const aggregator = new TraceAggregator({ config }, agent.collector, agent.harvester) - t.equal(aggregator.capacity, TOP_N) - t.end() + assert.equal(aggregator.capacity, TOP_N) }) - t.test('should track the top 20 slowest transactions if top_n is unconfigured', (t) => { - const { config, agent } = t.context + await t.test('should track the top 20 slowest transactions if top_n is unconfigured', (t) => { + const { config, agent } = t.nr const aggregator = new TraceAggregator({ config }, agent.collector, agent.harvester) - t.equal(aggregator.capacity, 20) - t.end() + assert.equal(aggregator.capacity, 20) }) - t.test('should track the slowest transaction in a harvest period if top_n is 0', (t) => { - const { config, agent } = t.context + await t.test('should track the slowest transaction in a harvest period if top_n is 0', (t) => { + const { config, agent } = t.nr config.transaction_tracer.top_n = 0 const aggregator = new TraceAggregator({ config }, agent.collector, agent.harvester) - t.equal(aggregator.capacity, 1) - t.end() + assert.equal(aggregator.capacity, 1) }) - t.test('should only save a trace for an existing name if new one is slower', (t) => { - const { config, agent } = t.context + await t.test('should only save a trace for an existing name if new one is slower', (t) => { + const { config, agent } = t.nr const URI = '/simple' const aggregator = new TraceAggregator({ config }, agent.collector, agent.harvester) aggregator.reported = 10 // needed to override "first 5" aggregator.add(createTransaction(agent, URI, 3000)) aggregator.add(createTransaction(agent, URI, 2100)) - t.equal(aggregator.requestTimes['WebTransaction/Uri/simple'], 3000) + assert.equal(aggregator.requestTimes['WebTransaction/Uri/simple'], 3000) aggregator.add(createTransaction(agent, URI, 4000)) - t.equal(aggregator.requestTimes['WebTransaction/Uri/simple'], 4000) - t.end() + assert.equal(aggregator.requestTimes['WebTransaction/Uri/simple'], 4000) }) - t.test('should only track transactions for the top N names', function (t) { - const { agent } = t.context + await t.test('should only track transactions for the top N names', function (t, end) { + const { agent } = t.nr agent.config.transaction_tracer.top_n = 5 agent.traces.capacity = 5 agent.traces.reported = 10 // needed to override "first 5" const maxTraces = 6 const txnCreator = (n, max, cb) => { - t.notOk(agent.traces.trace, 'trace before creation') + assert.ok(!agent.traces.trace, 'trace before creation') createTransaction(agent, `/test-${n}`, 8000) if (n !== 5) { - t.ok(agent.traces.trace, `trace ${n} to be collected`) + assert.ok(agent.traces.trace, `trace ${n} to be collected`) } else { - t.notOk(agent.traces.trace, 'trace 5 collected') + assert.ok(!agent.traces.trace, 'trace 5 collected') } agent.traces.once('finished_data_send-transaction_sample_data', (err) => cb(err, { idx: n, max }) ) agent.traces.send() - t.notOk(agent.traces.trace, 'trace after harvest') + assert.ok(!agent.traces.trace, 'trace after harvest') if (n === 5) { - t.end() + end() } } const finalCallback = (err) => { - t.error(err) + assert.ok(!err) const times = agent.traces.requestTimes - t.equal(times['WebTransaction/Uri/test-0'], 8000) - t.equal(times['WebTransaction/Uri/test-1'], 8000) - t.equal(times['WebTransaction/Uri/test-2'], 8000) - t.equal(times['WebTransaction/Uri/test-3'], 8000) - t.equal(times['WebTransaction/Uri/test-4'], 8000) - t.notOk(times['WebTransaction/Uri/test-5']) + assert.equal(times['WebTransaction/Uri/test-0'], 8000) + assert.equal(times['WebTransaction/Uri/test-1'], 8000) + assert.equal(times['WebTransaction/Uri/test-2'], 8000) + assert.equal(times['WebTransaction/Uri/test-3'], 8000) + assert.equal(times['WebTransaction/Uri/test-4'], 8000) + assert.ok(!times['WebTransaction/Uri/test-5']) } const testCallback = (err, props) => { - t.error(err) + assert.ok(!err) const { idx, max } = props const nextIdx = idx + 1 if (nextIdx >= max) { diff --git a/test/unit/transaction/tracer.test.js b/test/unit/transaction/tracer.test.js new file mode 100644 index 0000000000..bbc6ea2465 --- /dev/null +++ b/test/unit/transaction/tracer.test.js @@ -0,0 +1,145 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const assert = require('node:assert') +const test = require('node:test') +const helper = require('../../lib/agent_helper') +const Segment = require('../../../lib/transaction/trace/segment') + +const notRunningStates = ['stopped', 'stopping', 'errored'] +function beforeEach(ctx) { + ctx.nr = {} + const agent = helper.loadMockedAgent() + ctx.nr.tracer = agent.tracer + ctx.nr.agent = agent +} + +function afterEach(ctx) { + helper.unloadAgent(ctx.nr.agent) +} + +test('Tracer', async function (t) { + await t.test('#transactionProxy', async (t) => { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + await t.test('should create transaction', (t, end) => { + const { tracer } = t.nr + const wrapped = tracer.transactionProxy(() => { + const transaction = tracer.getTransaction() + assert.ok(transaction) + end() + }) + + wrapped() + }) + + await t.test('should not try to wrap a null handler', function (t) { + const { tracer } = t.nr + assert.equal(tracer.transactionProxy(null), null) + }) + + for (const agentState of notRunningStates) { + await t.test(`should not create transaction when agent state is ${agentState}`, (t) => { + const { tracer, agent } = t.nr + agent.setState(agentState) + + const wrapped = tracer.transactionProxy(() => { + const transaction = tracer.getTransaction() + assert.ok(!transaction) + }) + + wrapped() + }) + } + }) + + await t.test('#transactionNestProxy', async (t) => { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + await t.test('should create transaction', (t) => { + const { tracer } = t.nr + const wrapped = tracer.transactionNestProxy('web', () => { + const transaction = tracer.getTransaction() + assert.ok(transaction) + }) + + wrapped() + }) + + for (const agentState of notRunningStates) { + await t.test(`should not create transaction when agent state is ${agentState}`, (t) => { + const { tracer, agent } = t.nr + agent.setState(agentState) + + const wrapped = tracer.transactionNestProxy('web', () => { + const transaction = tracer.getTransaction() + assert.ok(!transaction) + }) + + wrapped() + }) + } + + await t.test( + 'when proxying a trace segment should not try to wrap a null handler', + function (t, end) { + const { tracer, agent } = t.nr + helper.runInTransaction(agent, function () { + assert.equal(tracer.wrapFunction('123', null, null), null) + end() + }) + } + ) + + await t.test( + 'when proxying a callback should not try to wrap a null handler', + function (t, end) { + const { tracer, agent } = t.nr + helper.runInTransaction(agent, function () { + assert.equal(tracer.bindFunction(null), null) + end() + }) + } + ) + + await t.test( + 'when handling immutable errors should not break in annotation process', + function (t, end) { + const expectErrMsg = 'FIREBOMB' + const { tracer, agent } = t.nr + helper.runInTransaction(agent, function (trans) { + function wrapMe() { + const err = new Error(expectErrMsg) + Object.freeze(err) + throw err + } + + assert.throws(() => { + const fn = tracer.bindFunction(wrapMe, new Segment(trans, 'name')) + fn() + }, /Error: FIREBOMB/) + end() + }) + } + ) + + await t.test( + 'when a transaction is created inside a transaction should reuse the existing transaction instead of nesting', + function (t, end) { + const { agent } = t.nr + helper.runInTransaction(agent, function (outerTransaction) { + const outerId = outerTransaction.id + helper.runInTransaction(agent, function (innerTransaction) { + const innerId = innerTransaction.id + + assert.equal(innerId, outerId) + end() + }) + }) + } + ) + }) +})