diff --git a/dev-packages/node-integration-tests/suites/hono-sdk/instrument.mjs b/dev-packages/node-integration-tests/suites/hono-sdk/instrument.mjs index c9aa645d7102..77c35dce5589 100644 --- a/dev-packages/node-integration-tests/suites/hono-sdk/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/hono-sdk/instrument.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/hono/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1.0, transport: loggingTransport, diff --git a/dev-packages/node-integration-tests/suites/hono-sdk/test.ts b/dev-packages/node-integration-tests/suites/hono-sdk/test.ts index 97c8b3481dc5..1366be964a95 100644 --- a/dev-packages/node-integration-tests/suites/hono-sdk/test.ts +++ b/dev-packages/node-integration-tests/suites/hono-sdk/test.ts @@ -7,17 +7,17 @@ describe('hono-sdk (Node)', () => { }); createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { - test('creates a transaction for a basic GET request', async () => { + test('creates a segment span for a basic GET request', async () => { const runner = createRunner() .expect({ - transaction: { - transaction: 'GET /', - contexts: { - trace: { - op: 'http.server', - status: 'ok', - }, - }, + span: container => { + expect(container.items.find(item => item.is_segment)).toMatchObject({ + name: 'GET /', + status: 'ok', + attributes: expect.objectContaining({ + 'sentry.op': { type: 'string', value: 'http.server' }, + }), + }); }, }) .start(); @@ -25,20 +25,18 @@ describe('hono-sdk (Node)', () => { await runner.completed(); }); - test('creates a transaction with a parametrized route name', async () => { + test('creates a segment span with a parametrized route name', async () => { const runner = createRunner() .expect({ - transaction: { - transaction: 'GET /hello/:name', - transaction_info: { - source: 'route', - }, - contexts: { - trace: { - op: 'http.server', - status: 'ok', - }, - }, + span: container => { + expect(container.items.find(item => item.is_segment)).toMatchObject({ + name: 'GET /hello/:name', + status: 'ok', + attributes: expect.objectContaining({ + 'sentry.op': { type: 'string', value: 'http.server' }, + 'sentry.segment.name.source': { type: 'string', value: 'route' }, + }), + }); }, }) .start(); @@ -48,7 +46,7 @@ describe('hono-sdk (Node)', () => { test('captures an error with the correct mechanism', async () => { const runner = createRunner() - .ignore('transaction') + .ignore('span') .expect({ event: { exception: { @@ -71,21 +69,20 @@ describe('hono-sdk (Node)', () => { await runner.completed(); }); - test('creates a transaction with internal_error status when an error occurs', async () => { + test('creates a segment span with error status when an error occurs', async () => { const runner = createRunner() .ignore('event') .expect({ - transaction: { - transaction: 'GET /error/:param', - contexts: { - trace: { - op: 'http.server', - status: 'internal_error', - data: expect.objectContaining({ - 'http.response.status_code': 500, - }), - }, - }, + span: container => { + expect(container.items.find(item => item.is_segment)).toMatchObject({ + name: 'GET /error/:param', + status: 'error', + attributes: expect.objectContaining({ + 'sentry.op': { type: 'string', value: 'http.server' }, + 'sentry.status.message': { type: 'string', value: 'internal_error' }, + 'http.response.status_code': { type: 'integer', value: 500 }, + }), + }); }, }) .start(); diff --git a/dev-packages/node-integration-tests/suites/tracing/fastify/instrument-error-handler.mjs b/dev-packages/node-integration-tests/suites/tracing/fastify/instrument-error-handler.mjs index 9683d33620b2..1fb2bfe6db8b 100644 --- a/dev-packages/node-integration-tests/suites/tracing/fastify/instrument-error-handler.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/fastify/instrument-error-handler.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/fastify/instrument-no-tracing.mjs b/dev-packages/node-integration-tests/suites/tracing/fastify/instrument-no-tracing.mjs index 03ea9ecc571a..6694d8977dd6 100644 --- a/dev-packages/node-integration-tests/suites/tracing/fastify/instrument-no-tracing.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/fastify/instrument-no-tracing.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', transport: loggingTransport, diff --git a/dev-packages/node-integration-tests/suites/tracing/fastify/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/fastify/instrument.mjs index 22bf57f14364..54c513068360 100644 --- a/dev-packages/node-integration-tests/suites/tracing/fastify/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/fastify/instrument.mjs @@ -2,7 +2,7 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static', + traceLifecycle: 'stream', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/fastify/test.ts b/dev-packages/node-integration-tests/suites/tracing/fastify/test.ts index 847aad7dc50c..7ace423c9745 100644 --- a/dev-packages/node-integration-tests/suites/tracing/fastify/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/fastify/test.ts @@ -7,85 +7,67 @@ describe('fastify v5 auto-instrumentation', () => { }); createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { - test('creates transaction with fastify hook, request-handler and manual spans', async () => { + test('creates segment span with fastify hook, request-handler and manual spans', async () => { const runner = createRunner() .expect({ - transaction: { - transaction: 'GET /test-transaction', - spans: expect.arrayContaining([ - expect.objectContaining({ - op: 'middleware', - origin: 'auto.http.fastify', - data: expect.objectContaining({ - 'fastify.type': 'hook', - 'sentry.op': 'middleware', - 'sentry.origin': 'auto.http.fastify', - }), - }), - // Route-level hooks have no `op`, so the span name falls back to `${hook} - ${handler}` - // using the original hook identifier (not the prefixed `hook.name` attribute). + span: container => { + expect(container.items.find(item => item.is_segment)?.name).toBe('GET /test-transaction'); + + expect(container.items).toContainEqual( expect.objectContaining({ - description: 'preHandler - routePreHandler', - origin: 'auto.http.fastify', - data: expect.objectContaining({ - 'fastify.type': 'route-hook', - 'hook.callback.name': 'routePreHandler', - 'sentry.origin': 'auto.http.fastify', + attributes: expect.objectContaining({ + 'fastify.type': { type: 'string', value: 'hook' }, + 'sentry.op': { type: 'string', value: 'middleware' }, + 'sentry.origin': { type: 'string', value: 'auto.http.fastify' }, }), }), + ); + + // Route-level hooks have no `op`, so the span name falls back to `${hook} - ${handler}` + // using the original hook identifier (not the prefixed `hook.name` attribute). + expect(container.items).toContainEqual( expect.objectContaining({ - op: 'handler', - origin: 'auto.http.fastify', - data: expect.objectContaining({ - 'sentry.op': 'handler', - 'sentry.origin': 'auto.http.fastify', + name: 'preHandler - routePreHandler', + attributes: expect.objectContaining({ + 'fastify.type': { type: 'string', value: 'route-hook' }, + 'hook.callback.name': { type: 'string', value: 'routePreHandler' }, + 'sentry.origin': { type: 'string', value: 'auto.http.fastify' }, }), }), - expect.objectContaining({ - description: 'test-span', - origin: 'manual', - }), - expect.objectContaining({ - description: 'child-span', - origin: 'manual', - }), - ]), - }, - }) - .start(); - runner.makeRequest('get', '/test-transaction'); - await runner.completed(); - }); + ); - test('names request handler spans after their route when span streaming is enabled', async () => { - const runner = createRunner() - .withEnv({ STREAMED: 'true' }) - .expect({ - span: container => { + // The request span and the route handler span are both named after the route. const handlerSpans = container.items.filter(item => item.attributes['sentry.op']?.value === 'handler'); - - // The request span and the route handler span. expect(handlerSpans).toHaveLength(2); for (const span of handlerSpans) { expect(span.name).toBe('/test-transaction'); // The name has to stay in step with the attribute it comes from. expect(span.attributes['http.route']?.value).toBe('/test-transaction'); + expect(span.attributes['sentry.origin']?.value).toBe('auto.http.fastify'); } - // Spans of other ops keep their names. - expect(container.items.find(item => item.name === 'preHandler - routePreHandler')).toBeDefined(); + expect(container.items).toContainEqual( + expect.objectContaining({ + name: 'test-span', + attributes: expect.objectContaining({ 'sentry.origin': { type: 'string', value: 'manual' } }), + }), + ); + expect(container.items).toContainEqual( + expect.objectContaining({ + name: 'child-span', + attributes: expect.objectContaining({ 'sentry.origin': { type: 'string', value: 'manual' } }), + }), + ); }, }) .start(); - - await runner.makeRequest('get', '/test-transaction'); - + runner.makeRequest('get', '/test-transaction'); await runner.completed(); }); test('captures errors thrown in route handlers', async () => { const runner = createRunner() - .ignore('transaction') + .ignore('span') .expect({ event: { exception: { @@ -133,7 +115,7 @@ describe('fastify v5 auto-instrumentation', () => { (createRunner, test) => { test('shouldHandleError override works', async () => { const runner = createRunner() - .ignore('transaction') + .ignore('span') .expect({ event: { exception: { diff --git a/dev-packages/node-integration-tests/suites/tracing/hapi/instrument-should-handle-error.mjs b/dev-packages/node-integration-tests/suites/tracing/hapi/instrument-should-handle-error.mjs index 193ef0a6fb36..ae93b7ea3029 100644 --- a/dev-packages/node-integration-tests/suites/tracing/hapi/instrument-should-handle-error.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/hapi/instrument-should-handle-error.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/hapi/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/hapi/instrument.mjs index 22bf57f14364..54c513068360 100644 --- a/dev-packages/node-integration-tests/suites/tracing/hapi/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/hapi/instrument.mjs @@ -2,7 +2,7 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static', + traceLifecycle: 'stream', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts b/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts index e6d0f3369c3a..24a9d1b2adba 100644 --- a/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts @@ -8,25 +8,6 @@ describe('hapi auto-instrumentation', () => { const origin = 'auto.http.hapi'; - const EXPECTED_TRANSACTION = { - transaction: 'GET /', - spans: expect.arrayContaining([ - expect.objectContaining({ - data: expect.objectContaining({ - 'http.route': '/', - 'http.request.method': 'GET', - 'hapi.type': 'router', - 'sentry.origin': origin, - 'sentry.op': 'router', - }), - description: 'GET /', - op: 'router', - origin, - status: 'ok', - }), - ]), - }; - const EXPECTED_ERROR_EVENT = { exception: { values: [ @@ -40,77 +21,77 @@ describe('hapi auto-instrumentation', () => { createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { test('should auto-instrument `@hapi/hapi` package.', async () => { - const runner = createRunner().expect({ transaction: EXPECTED_TRANSACTION }).start(); - runner.makeRequest('get', '/'); - await runner.completed(); - }); - - test('should instrument plugin routes and server extensions.', async () => { const runner = createRunner() .expect({ - transaction: { - transaction: 'GET /plugin-route', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: 'GET /plugin-route', - op: 'handler', - origin, - data: expect.objectContaining({ - 'http.route': '/plugin-route', - 'hapi.type': 'plugin', - 'hapi.plugin.name': 'testPlugin', - 'sentry.op': 'handler', - 'sentry.origin': origin, - }), - }), + span: container => { + expect(container.items.find(item => item.is_segment)?.name).toBe('GET /'); + + // Router spans are named after the route alone, without the `GET ` prefix. + expect(container.items).toContainEqual( expect.objectContaining({ - description: 'ext - onPreResponse', - op: 'middleware', - origin, - data: expect.objectContaining({ - 'hapi.type': 'server.ext', - 'server.ext.type': 'onPreResponse', - 'sentry.op': 'middleware', - 'sentry.origin': origin, + name: '/', + status: 'ok', + attributes: expect.objectContaining({ + 'http.route': { type: 'string', value: '/' }, + 'http.request.method': { type: 'string', value: 'GET' }, + 'hapi.type': { type: 'string', value: 'router' }, + 'sentry.origin': { type: 'string', value: origin }, + 'sentry.op': { type: 'string', value: 'router' }, }), }), - ]), + ); }, }) .start(); - runner.makeRequest('get', '/plugin-route'); + runner.makeRequest('get', '/'); await runner.completed(); }); - test('names request handler spans after their route when span streaming is enabled', async () => { + test('should instrument plugin routes and server extensions.', async () => { const runner = createRunner() - .withEnv({ STREAMED: 'true' }) .expect({ span: container => { - const handlerSpan = container.items.find(item => item.attributes['sentry.op']?.value === 'handler'); + expect(container.items.find(item => item.is_segment)?.name).toBe('GET /plugin-route'); - // The route alone, without the `GET ` prefix the static name carries. - expect(handlerSpan?.name).toBe('/plugin-route'); - // The name has to stay in step with the attribute it comes from. - expect(handlerSpan?.attributes['http.route']?.value).toBe('/plugin-route'); - expect(handlerSpan?.attributes['hapi.type']?.value).toBe('plugin'); + const handlerSpan = container.items.find(item => item.attributes['sentry.op']?.value === 'handler'); + expect(handlerSpan).toMatchObject({ + // The route alone, without the `GET ` prefix the static name carried. + name: '/plugin-route', + attributes: expect.objectContaining({ + // The name has to stay in step with the attribute it comes from. + 'http.route': { type: 'string', value: '/plugin-route' }, + 'hapi.type': { type: 'string', value: 'plugin' }, + 'hapi.plugin.name': { type: 'string', value: 'testPlugin' }, + 'sentry.op': { type: 'string', value: 'handler' }, + 'sentry.origin': { type: 'string', value: origin }, + }), + }); // Spans of other ops keep their names. - expect(container.items.find(item => item.name === 'ext - onPreResponse')).toBeDefined(); + expect(container.items).toContainEqual( + expect.objectContaining({ + name: 'ext - onPreResponse', + attributes: expect.objectContaining({ + 'hapi.type': { type: 'string', value: 'server.ext' }, + 'server.ext.type': { type: 'string', value: 'onPreResponse' }, + 'sentry.op': { type: 'string', value: 'middleware' }, + 'sentry.origin': { type: 'string', value: origin }, + }), + }), + ); }, }) .start(); - - await runner.makeRequest('get', '/plugin-route'); - + runner.makeRequest('get', '/plugin-route'); await runner.completed(); }); test('should handle returned plain errors in routes.', async () => { const runner = createRunner() + .unordered() .expect({ - transaction: { - transaction: 'GET /error', + span: container => { + expect(container.items.find(item => item.is_segment)?.name).toBe('GET /error'); }, }) .expect({ event: EXPECTED_ERROR_EVENT }) @@ -127,7 +108,7 @@ describe('hapi auto-instrumentation', () => { transaction: 'GET /error/{id}', }, }) - .ignore('transaction') + .ignore('span') .start(); runner.makeRequest('get', '/error/123', { expectError: true }); await runner.completed(); @@ -135,9 +116,10 @@ describe('hapi auto-instrumentation', () => { test('should handle returned Boom errors in routes.', async () => { const runner = createRunner() + .unordered() .expect({ - transaction: { - transaction: 'GET /boom-error', + span: container => { + expect(container.items.find(item => item.is_segment)?.name).toBe('GET /boom-error'); }, }) .expect({ event: EXPECTED_ERROR_EVENT }) @@ -148,9 +130,10 @@ describe('hapi auto-instrumentation', () => { test('should handle promise rejections in routes.', async () => { const runner = createRunner() + .unordered() .expect({ - transaction: { - transaction: 'GET /promise-error', + span: container => { + expect(container.items.find(item => item.is_segment)?.name).toBe('GET /promise-error'); }, }) .expect({ event: EXPECTED_ERROR_EVENT }) @@ -172,7 +155,7 @@ describe('hapi auto-instrumentation', () => { (createRunner, test) => { test('integration `shouldHandleError` overrides an earlier default-valued `setupHapiErrorHandler`', async () => { const runner = createRunner() - .ignore('transaction') + .ignore('span') .expect({ event: { exception: { diff --git a/dev-packages/node-integration-tests/suites/tracing/koa/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/koa/instrument.mjs index 170ad6f6a702..46a27dd03b74 100644 --- a/dev-packages/node-integration-tests/suites/tracing/koa/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/koa/instrument.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/koa/test.ts b/dev-packages/node-integration-tests/suites/tracing/koa/test.ts index 05f826554ab2..667717720656 100644 --- a/dev-packages/node-integration-tests/suites/tracing/koa/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/koa/test.ts @@ -33,36 +33,36 @@ describe('koa auto-instrumentation', () => { test('should auto-instrument `koa` router and middleware layers.', async () => { const runner = createRunner() .expect({ - transaction: { - transaction: 'GET /', - spans: expect.arrayContaining([ - // Router layer span (from `@koa/router`), carrying the matched route. + span: container => { + expect(container.items.find(item => item.is_segment)?.name).toBe('GET /'); + + // Router layer span (from `@koa/router`), carrying the matched route. + expect(container.items).toContainEqual( expect.objectContaining({ - description: '/', - op: 'router', - origin, - data: expect.objectContaining({ - 'http.route': '/', - 'koa.type': 'router', - 'koa.name': '/', - 'sentry.op': 'router', - 'sentry.origin': origin, + name: '/', + attributes: expect.objectContaining({ + 'http.route': { type: 'string', value: '/' }, + 'koa.type': { type: 'string', value: 'router' }, + 'koa.name': { type: 'string', value: '/' }, + 'sentry.op': { type: 'string', value: 'router' }, + 'sentry.origin': { type: 'string', value: origin }, }), }), - // Plain middleware span. + ); + + // Plain middleware span. + expect(container.items).toContainEqual( expect.objectContaining({ - description: 'simpleMiddleware', - op: 'middleware', - origin, - data: expect.objectContaining({ - 'koa.type': 'middleware', - 'koa.name': 'simpleMiddleware', - 'code.function.name': 'simpleMiddleware', - 'sentry.op': 'middleware', - 'sentry.origin': origin, + name: 'simpleMiddleware', + attributes: expect.objectContaining({ + 'koa.type': { type: 'string', value: 'middleware' }, + 'koa.name': { type: 'string', value: 'simpleMiddleware' }, + 'code.function.name': { type: 'string', value: 'simpleMiddleware' }, + 'sentry.op': { type: 'string', value: 'middleware' }, + 'sentry.origin': { type: 'string', value: origin }, }), }), - ]), + ); }, }) .start(); @@ -70,25 +70,24 @@ describe('koa auto-instrumentation', () => { await runner.completed(); }); - test('should assign a parameterized transaction name.', async () => { + test('should assign a parameterized segment name.', async () => { const runner = createRunner() .expect({ - transaction: { - transaction: 'GET /test-param/:id', - spans: expect.arrayContaining([ + span: container => { + expect(container.items.find(item => item.is_segment)?.name).toBe('GET /test-param/:id'); + + expect(container.items).toContainEqual( expect.objectContaining({ - description: '/test-param/:id', - op: 'router', - origin, - data: expect.objectContaining({ - 'http.route': '/test-param/:id', - 'koa.type': 'router', - 'koa.name': '/test-param/:id', - 'sentry.op': 'router', - 'sentry.origin': origin, + name: '/test-param/:id', + attributes: expect.objectContaining({ + 'http.route': { type: 'string', value: '/test-param/:id' }, + 'koa.type': { type: 'string', value: 'router' }, + 'koa.name': { type: 'string', value: '/test-param/:id' }, + 'sentry.op': { type: 'string', value: 'router' }, + 'sentry.origin': { type: 'string', value: origin }, }), }), - ]), + ); }, }) .start(); @@ -99,7 +98,11 @@ describe('koa auto-instrumentation', () => { test('should capture errors thrown in routes via the koa error handler.', async () => { const runner = createRunner() .unordered() - .expect({ transaction: { transaction: 'GET /error' } }) + .expect({ + span: container => { + expect(container.items.find(item => item.is_segment)?.name).toBe('GET /error'); + }, + }) .expect({ event: EXPECTED_ERROR_EVENT }) .start(); runner.makeRequest('get', '/error', { expectError: true }); diff --git a/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/instrument.mjs index 170ad6f6a702..46a27dd03b74 100644 --- a/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/instrument.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/test.ts b/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/test.ts index 99b9f263fcaf..78ba2f59caf6 100644 --- a/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/test.ts @@ -11,18 +11,15 @@ describe('lru-memoizer', () => { test('keeps outer context inside the memoized inner functions', async () => { await createTestRunner() .expect({ - transaction: { - transaction: 'test-name', - contexts: { - trace: expect.objectContaining({ - op: 'run', - data: expect.objectContaining({ - 'sentry.op': 'run', - 'sentry.origin': 'manual', - 'memoized.context_preserved': true, - }), + span: container => { + expect(container.items.find(item => item.is_segment)).toMatchObject({ + name: 'test-name', + attributes: expect.objectContaining({ + 'sentry.op': { type: 'string', value: 'run' }, + 'sentry.origin': { type: 'string', value: 'manual' }, + 'memoized.context_preserved': { type: 'boolean', value: true }, }), - }, + }); }, }) .start() @@ -33,20 +30,20 @@ describe('lru-memoizer', () => { // CJS-only: the parallel scenario is flaky in ESM (see #21729). createCjsTests(__dirname, 'scenario-parallel.mjs', 'instrument.mjs', (createTestRunner, test) => { test('keeps each span context across parallel memoized requests', async () => { - // Each parallel request emits a transaction whose callback must have run in its own context. - // Two identical expectations keep this order-independent. - const expectation = { - transaction: { - contexts: { - trace: expect.objectContaining({ - op: expect.stringMatching(/^(first|second)$/), - data: expect.objectContaining({ 'memoized.context_preserved': true }), - }), + // Both root spans share the isolation scope's trace, so they are flushed in one envelope. + // Each callback must have run in its own span's context. + await createTestRunner() + .expect({ + span: container => { + const segmentSpans = container.items.filter(item => item.is_segment); + expect(segmentSpans.map(span => span.attributes['sentry.op']?.value).sort()).toEqual(['first', 'second']); + for (const span of segmentSpans) { + expect(span.attributes['memoized.context_preserved']).toEqual({ type: 'boolean', value: true }); + } }, - }, - }; - - await createTestRunner().expect(expectation).expect(expectation).start().completed(); + }) + .start() + .completed(); }); }); });