From 529fdf6299ab2084ee5039721df4c3ebe68fc365 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 3 Jul 2026 14:32:10 +0200 Subject: [PATCH] test(node): Migrate fs & contextLines-filename integration tests to helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move two node integration suites onto the shared runner helpers: - `fs-instrumentation`: split the three `server*.ts` into one `scenario.mjs` (full express app) plus three `instrument.mjs` variants (full / record-paths-only / record-errors-only), driven via `createCjsTests` with `copyPaths: ['fixtures']`. Runs in CJS as before. - `contextLines/filename-with-spaces`: use `createEsmAndCjsTests` off the existing `scenario with space.mjs` + `instrument.mjs`, replacing the hand-written `.cjs`. CJS assertions updated to the auto-generated file structure (inline init removed → context lines shift accordingly). `contextLines/memory-leak` is intentionally left on its current `createRunner` setup: its lsof-based file-handle leak check derives paths from `__dirname`, which the helper's tmp-dir indirection breaks, so migrating it would silently neuter the regression check (issue #14892). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../scenario with space.cjs | 12 - .../contextLines/filename-with-spaces/test.ts | 118 +-- .../instrument-record-errors-only.mjs | 15 + .../instrument-record-paths-only.mjs | 15 + .../suites/fs-instrumentation/instrument.mjs | 15 + .../{server.ts => scenario.mjs} | 28 +- .../server-record-errors-only.ts | 39 - .../server-record-paths-only.ts | 39 - .../suites/fs-instrumentation/test.ts | 760 +++++++++--------- 9 files changed, 489 insertions(+), 552 deletions(-) delete mode 100644 dev-packages/node-integration-tests/suites/contextLines/filename-with-spaces/scenario with space.cjs create mode 100644 dev-packages/node-integration-tests/suites/fs-instrumentation/instrument-record-errors-only.mjs create mode 100644 dev-packages/node-integration-tests/suites/fs-instrumentation/instrument-record-paths-only.mjs create mode 100644 dev-packages/node-integration-tests/suites/fs-instrumentation/instrument.mjs rename dev-packages/node-integration-tests/suites/fs-instrumentation/{server.ts => scenario.mjs} (85%) delete mode 100644 dev-packages/node-integration-tests/suites/fs-instrumentation/server-record-errors-only.ts delete mode 100644 dev-packages/node-integration-tests/suites/fs-instrumentation/server-record-paths-only.ts diff --git a/dev-packages/node-integration-tests/suites/contextLines/filename-with-spaces/scenario with space.cjs b/dev-packages/node-integration-tests/suites/contextLines/filename-with-spaces/scenario with space.cjs deleted file mode 100644 index 41618eb3fee5..000000000000 --- a/dev-packages/node-integration-tests/suites/contextLines/filename-with-spaces/scenario with space.cjs +++ /dev/null @@ -1,12 +0,0 @@ -const Sentry = require('@sentry/node'); -const { loggingTransport } = require('@sentry-internal/node-integration-tests'); - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - transport: loggingTransport, -}); - -Sentry.captureException(new Error('Test Error')); - -// some more post context diff --git a/dev-packages/node-integration-tests/suites/contextLines/filename-with-spaces/test.ts b/dev-packages/node-integration-tests/suites/contextLines/filename-with-spaces/test.ts index 276e8930b1ea..4e0cf6cd7f4f 100644 --- a/dev-packages/node-integration-tests/suites/contextLines/filename-with-spaces/test.ts +++ b/dev-packages/node-integration-tests/suites/contextLines/filename-with-spaces/test.ts @@ -1,85 +1,49 @@ -import { join } from 'path'; -import { describe, expect, test } from 'vitest'; -import { createRunner } from '../../../utils/runner'; +import { afterAll, describe, expect } from 'vitest'; +import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; -describe('ContextLines integration in ESM', () => { - test('reads encoded context lines from filenames with spaces', async () => { - expect.assertions(1); - const instrumentPath = join(__dirname, 'instrument.mjs'); - - await createRunner(__dirname, 'scenario with space.mjs') - .withInstrument(instrumentPath) - .expect({ - event: { - exception: { - values: [ - { - value: 'Test Error', - stacktrace: { - frames: expect.arrayContaining([ - { - filename: expect.stringMatching(/\/scenario with space.mjs$/), - context_line: "Sentry.captureException(new Error('Test Error'));", - pre_context: ["import * as Sentry from '@sentry/node';", ''], - post_context: ['', '// some more post context'], - colno: 25, - lineno: 3, - function: '?', - in_app: true, - module: 'scenario with space', - }, - ]), - }, - }, - ], - }, - }, - }) - .start() - .completed(); +describe('ContextLines integration - filename with spaces', () => { + afterAll(() => { + cleanupChildProcesses(); }); -}); -describe('ContextLines integration in CJS', () => { - test('reads context lines from filenames with spaces', async () => { - expect.assertions(1); + createEsmAndCjsTests(__dirname, 'scenario with space.mjs', 'instrument.mjs', (createRunner, test, mode) => { + test('reads encoded context lines from filenames with spaces', async () => { + expect.assertions(1); - await createRunner(__dirname, 'scenario with space.cjs') - .expect({ - event: { - exception: { - values: [ - { - value: 'Test Error', - stacktrace: { - frames: expect.arrayContaining([ - { - filename: expect.stringMatching(/\/scenario with space.cjs$/), - context_line: "Sentry.captureException(new Error('Test Error'));", - pre_context: [ - '', - 'Sentry.init({', - " dsn: 'https://public@dsn.ingest.sentry.io/1337',", - " release: '1.0',", - ' transport: loggingTransport,', - '});', - '', - ], - post_context: ['', '// some more post context'], - colno: 25, - lineno: 10, - function: 'Object.?', - in_app: true, - module: 'scenario with space', - }, - ]), + await createRunner() + .expect({ + event: { + exception: { + values: [ + { + value: 'Test Error', + stacktrace: { + frames: expect.arrayContaining([ + { + filename: expect.stringMatching( + mode === 'esm' ? /\/scenario with space.mjs$/ : /\/scenario with space.cjs$/, + ), + context_line: "Sentry.captureException(new Error('Test Error'));", + pre_context: + mode === 'esm' + ? ["import * as Sentry from '@sentry/node';", ''] + : ["const Sentry = require('@sentry/node');", ''], + post_context: ['', '// some more post context'], + colno: 25, + lineno: 3, + function: mode === 'esm' ? '?' : 'Object.?', + in_app: true, + module: 'scenario with space', + }, + ]), + }, }, - }, - ], + ], + }, }, - }, - }) - .start() - .completed(); + }) + .start() + .completed(); + }); }); }); diff --git a/dev-packages/node-integration-tests/suites/fs-instrumentation/instrument-record-errors-only.mjs b/dev-packages/node-integration-tests/suites/fs-instrumentation/instrument-record-errors-only.mjs new file mode 100644 index 000000000000..eff54f5b1ba7 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/fs-instrumentation/instrument-record-errors-only.mjs @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + transport: loggingTransport, + tracesSampleRate: 1, + integrations: [ + // Only record error messages - file paths must NOT be recorded + Sentry.fsIntegration({ + recordErrorMessagesAsSpanAttributes: true, + }), + ], +}); diff --git a/dev-packages/node-integration-tests/suites/fs-instrumentation/instrument-record-paths-only.mjs b/dev-packages/node-integration-tests/suites/fs-instrumentation/instrument-record-paths-only.mjs new file mode 100644 index 000000000000..05beff418e87 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/fs-instrumentation/instrument-record-paths-only.mjs @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + transport: loggingTransport, + tracesSampleRate: 1, + integrations: [ + // Only record file paths - error messages must NOT be recorded + Sentry.fsIntegration({ + recordFilePaths: true, + }), + ], +}); diff --git a/dev-packages/node-integration-tests/suites/fs-instrumentation/instrument.mjs b/dev-packages/node-integration-tests/suites/fs-instrumentation/instrument.mjs new file mode 100644 index 000000000000..f17a1e3bd449 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/fs-instrumentation/instrument.mjs @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + transport: loggingTransport, + tracesSampleRate: 1, + integrations: [ + Sentry.fsIntegration({ + recordFilePaths: true, + recordErrorMessagesAsSpanAttributes: true, + }), + ], +}); diff --git a/dev-packages/node-integration-tests/suites/fs-instrumentation/server.ts b/dev-packages/node-integration-tests/suites/fs-instrumentation/scenario.mjs similarity index 85% rename from dev-packages/node-integration-tests/suites/fs-instrumentation/server.ts rename to dev-packages/node-integration-tests/suites/fs-instrumentation/scenario.mjs index 7e510a906def..4cf58c2fa122 100644 --- a/dev-packages/node-integration-tests/suites/fs-instrumentation/server.ts +++ b/dev-packages/node-integration-tests/suites/fs-instrumentation/scenario.mjs @@ -1,19 +1,5 @@ import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - transport: loggingTransport, - tracesSampleRate: 1, - integrations: [ - Sentry.fsIntegration({ - recordFilePaths: true, - recordErrorMessagesAsSpanAttributes: true, - }), - ], -}); - +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import express from 'express'; import * as fs from 'fs'; import * as os from 'os'; @@ -32,7 +18,7 @@ app.get('/readFile-error', async (_, res) => { }); app.get('/readFile', async (_, res) => { - await new Promise(resolve => { + await new Promise(resolve => { fs.readFile(path.join(__dirname, 'fixtures', 'some-file.txt'), 'utf-8', () => { resolve(); }); @@ -43,7 +29,7 @@ app.get('/readFile', async (_, res) => { }); app.get('/copyFile', async (_, res) => { - await new Promise(resolve => { + await new Promise(resolve => { fs.copyFile( path.join(__dirname, 'fixtures', 'some-file.txt'), path.join(__dirname, 'fixtures', 'some-file.txt.copy'), @@ -64,7 +50,7 @@ app.get('/copyFile', async (_, res) => { }); app.get('/link', async (_, res) => { - await new Promise(resolve => { + await new Promise(resolve => { fs.link( path.join(__dirname, 'fixtures', 'some-file.txt'), path.join(__dirname, 'fixtures', 'some-file.txt.link'), @@ -92,7 +78,7 @@ app.get('/link', async (_, res) => { }); app.get('/mkdtemp', async (_, res) => { - await new Promise(resolve => { + await new Promise(resolve => { fs.mkdtemp(path.join(os.tmpdir(), 'foo-'), () => { resolve(); }); @@ -104,7 +90,7 @@ app.get('/mkdtemp', async (_, res) => { }); app.get('/exists', async (_, res) => { - await new Promise(resolve => { + await new Promise(resolve => { fs.exists(path.join(__dirname, 'fixtures', 'some-file.txt'), () => { resolve(); }); @@ -114,7 +100,7 @@ app.get('/exists', async (_, res) => { }); app.get('/symlink', async (_, res) => { - await new Promise(resolve => { + await new Promise(resolve => { fs.symlink( path.join(__dirname, 'fixtures', 'some-file.txt'), path.join(__dirname, 'fixtures', 'some-file.txt.symlink'), diff --git a/dev-packages/node-integration-tests/suites/fs-instrumentation/server-record-errors-only.ts b/dev-packages/node-integration-tests/suites/fs-instrumentation/server-record-errors-only.ts deleted file mode 100644 index 4b4d87398148..000000000000 --- a/dev-packages/node-integration-tests/suites/fs-instrumentation/server-record-errors-only.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - transport: loggingTransport, - tracesSampleRate: 1, - integrations: [ - // Only record error messages - file paths must NOT be recorded - Sentry.fsIntegration({ - recordErrorMessagesAsSpanAttributes: true, - }), - ], -}); - -import express from 'express'; -import * as fs from 'fs'; -import * as path from 'path'; - -const app = express(); - -app.get('/readFile', async (_, res) => { - await fs.promises.readFile(path.join(__dirname, 'fixtures', 'some-file.txt'), 'utf-8'); - res.send('done'); -}); - -app.get('/readFile-error', async (_, res) => { - try { - await fs.promises.readFile(path.join(__dirname, 'fixtures', 'some-file-that-doesnt-exist.txt'), 'utf-8'); - } catch { - // noop - } - res.send('done'); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/fs-instrumentation/server-record-paths-only.ts b/dev-packages/node-integration-tests/suites/fs-instrumentation/server-record-paths-only.ts deleted file mode 100644 index 385e53faae0d..000000000000 --- a/dev-packages/node-integration-tests/suites/fs-instrumentation/server-record-paths-only.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - transport: loggingTransport, - tracesSampleRate: 1, - integrations: [ - // Only record file paths - error messages must NOT be recorded - Sentry.fsIntegration({ - recordFilePaths: true, - }), - ], -}); - -import express from 'express'; -import * as fs from 'fs'; -import * as path from 'path'; - -const app = express(); - -app.get('/readFile', async (_, res) => { - await fs.promises.readFile(path.join(__dirname, 'fixtures', 'some-file.txt'), 'utf-8'); - res.send('done'); -}); - -app.get('/readFile-error', async (_, res) => { - try { - await fs.promises.readFile(path.join(__dirname, 'fixtures', 'some-file-that-doesnt-exist.txt'), 'utf-8'); - } catch { - // noop - } - res.send('done'); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/fs-instrumentation/test.ts b/dev-packages/node-integration-tests/suites/fs-instrumentation/test.ts index e15bf3f06634..281832912756 100644 --- a/dev-packages/node-integration-tests/suites/fs-instrumentation/test.ts +++ b/dev-packages/node-integration-tests/suites/fs-instrumentation/test.ts @@ -1,389 +1,421 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/node'; -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../utils/runner'; +import { afterAll, describe, expect } from 'vitest'; +import { cleanupChildProcesses, createCjsTests } from '../../utils/runner'; -afterAll(() => { - cleanupChildProcesses(); -}); +describe('fs instrumentation', () => { + afterAll(() => { + cleanupChildProcesses(); + }); -test('should create spans for fs operations that take target argument', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - transaction: { - transaction: 'GET /readFile-error', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: 'fs.readFile', - op: 'file', - status: 'internal_error', - data: { - fs_error: expect.stringMatching('ENOENT: no such file or directory,'), - path_argument: expect.stringMatching('/fixtures/some-file-that-doesnt-exist.txt'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - ]), - }, - }) - .start(); + describe('records file paths and error messages', () => { + createCjsTests( + __dirname, + 'scenario.mjs', + 'instrument.mjs', + (createRunner, test) => { + test('should create spans for fs operations that take target argument', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /readFile-error', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'fs.readFile', + op: 'file', + status: 'internal_error', + data: { + fs_error: expect.stringMatching('ENOENT: no such file or directory,'), + path_argument: expect.stringMatching('/fixtures/some-file-that-doesnt-exist.txt'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + ]), + }, + }) + .start(); - const result = await runner.makeRequest('get', '/readFile-error'); - expect(result).toEqual('done'); - await runner.completed(); -}); + const result = await runner.makeRequest('get', '/readFile-error'); + expect(result).toEqual('done'); + await runner.completed(); + }); -test('should create spans for fs operations that take one path', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - transaction: { - transaction: 'GET /readFile', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: 'fs.readFile', - op: 'file', - status: 'ok', - data: { - path_argument: expect.stringMatching('/fixtures/some-file.txt'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - expect.objectContaining({ - description: 'fs.readFile', - op: 'file', - status: 'ok', - data: { - path_argument: expect.stringMatching('/fixtures/some-file-promises.txt'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - expect.objectContaining({ - description: 'fs.readFile', - op: 'file', - status: 'ok', - data: { - path_argument: expect.stringMatching('/fixtures/some-file-promisify.txt'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - ]), - }, - }) - .start(); + test('should create spans for fs operations that take one path', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /readFile', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'fs.readFile', + op: 'file', + status: 'ok', + data: { + path_argument: expect.stringMatching('/fixtures/some-file.txt'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + expect.objectContaining({ + description: 'fs.readFile', + op: 'file', + status: 'ok', + data: { + path_argument: expect.stringMatching('/fixtures/some-file-promises.txt'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + expect.objectContaining({ + description: 'fs.readFile', + op: 'file', + status: 'ok', + data: { + path_argument: expect.stringMatching('/fixtures/some-file-promisify.txt'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + ]), + }, + }) + .start(); - const result = await runner.makeRequest('get', '/readFile'); - expect(result).toEqual('done'); - await runner.completed(); -}); + const result = await runner.makeRequest('get', '/readFile'); + expect(result).toEqual('done'); + await runner.completed(); + }); -test('should create spans for fs operations that take src and dest arguments', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - transaction: { - transaction: 'GET /copyFile', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: 'fs.copyFile', - op: 'file', - status: 'ok', - data: { - src_argument: expect.stringMatching('/fixtures/some-file.txt'), - dest_argument: expect.stringMatching('/fixtures/some-file.txt.copy'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - expect.objectContaining({ - description: 'fs.copyFile', - op: 'file', - status: 'ok', - data: { - src_argument: expect.stringMatching('/fixtures/some-file-promises.txt'), - dest_argument: expect.stringMatching('/fixtures/some-file-promises.txt.copy'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - expect.objectContaining({ - description: 'fs.copyFile', - op: 'file', - status: 'ok', - data: { - src_argument: expect.stringMatching('/fixtures/some-file-promisify.txt'), - dest_argument: expect.stringMatching('/fixtures/some-file-promisify.txt.copy'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - ]), - }, - }) - .start(); + test('should create spans for fs operations that take src and dest arguments', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /copyFile', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'fs.copyFile', + op: 'file', + status: 'ok', + data: { + src_argument: expect.stringMatching('/fixtures/some-file.txt'), + dest_argument: expect.stringMatching('/fixtures/some-file.txt.copy'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + expect.objectContaining({ + description: 'fs.copyFile', + op: 'file', + status: 'ok', + data: { + src_argument: expect.stringMatching('/fixtures/some-file-promises.txt'), + dest_argument: expect.stringMatching('/fixtures/some-file-promises.txt.copy'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + expect.objectContaining({ + description: 'fs.copyFile', + op: 'file', + status: 'ok', + data: { + src_argument: expect.stringMatching('/fixtures/some-file-promisify.txt'), + dest_argument: expect.stringMatching('/fixtures/some-file-promisify.txt.copy'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + ]), + }, + }) + .start(); - const result = await runner.makeRequest('get', '/copyFile'); - expect(result).toEqual('done'); - await runner.completed(); -}); + const result = await runner.makeRequest('get', '/copyFile'); + expect(result).toEqual('done'); + await runner.completed(); + }); -test('should create spans for fs operations that take existing path and new path arguments', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - transaction: { - transaction: 'GET /link', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: 'fs.link', - op: 'file', - status: 'ok', - data: { - existing_path_argument: expect.stringMatching('/fixtures/some-file.txt'), - new_path_argument: expect.stringMatching('/fixtures/some-file.txt.link'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - expect.objectContaining({ - description: 'fs.link', - op: 'file', - status: 'ok', - data: { - existing_path_argument: expect.stringMatching('/some-file-promises.txt'), - new_path_argument: expect.stringMatching('/some-file-promises.txt.link'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - expect.objectContaining({ - description: 'fs.link', - op: 'file', - status: 'ok', - data: { - existing_path_argument: expect.stringMatching('/fixtures/some-file-promisify.txt'), - new_path_argument: expect.stringMatching('/fixtures/some-file-promisify.txt.link'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - ]), - }, - }) - .start(); + test('should create spans for fs operations that take existing path and new path arguments', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /link', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'fs.link', + op: 'file', + status: 'ok', + data: { + existing_path_argument: expect.stringMatching('/fixtures/some-file.txt'), + new_path_argument: expect.stringMatching('/fixtures/some-file.txt.link'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + expect.objectContaining({ + description: 'fs.link', + op: 'file', + status: 'ok', + data: { + existing_path_argument: expect.stringMatching('/some-file-promises.txt'), + new_path_argument: expect.stringMatching('/some-file-promises.txt.link'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + expect.objectContaining({ + description: 'fs.link', + op: 'file', + status: 'ok', + data: { + existing_path_argument: expect.stringMatching('/fixtures/some-file-promisify.txt'), + new_path_argument: expect.stringMatching('/fixtures/some-file-promisify.txt.link'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + ]), + }, + }) + .start(); - const result = await runner.makeRequest('get', '/link'); - expect(result).toEqual('done'); - await runner.completed(); -}); + const result = await runner.makeRequest('get', '/link'); + expect(result).toEqual('done'); + await runner.completed(); + }); -test('should create spans for fs operations that take prefix argument', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - transaction: { - transaction: 'GET /mkdtemp', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: 'fs.mkdtemp', - op: 'file', - status: 'ok', - data: { - prefix_argument: expect.stringMatching('/foo-'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - expect.objectContaining({ - description: 'fs.mkdtemp', - op: 'file', - status: 'ok', - data: { - prefix_argument: expect.stringMatching('/foo-'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - expect.objectContaining({ - description: 'fs.mkdtemp', - op: 'file', - status: 'ok', - data: { - prefix_argument: expect.stringMatching('/foo-'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - ]), - }, - }) - .start(); + test('should create spans for fs operations that take prefix argument', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /mkdtemp', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'fs.mkdtemp', + op: 'file', + status: 'ok', + data: { + prefix_argument: expect.stringMatching('/foo-'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + expect.objectContaining({ + description: 'fs.mkdtemp', + op: 'file', + status: 'ok', + data: { + prefix_argument: expect.stringMatching('/foo-'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + expect.objectContaining({ + description: 'fs.mkdtemp', + op: 'file', + status: 'ok', + data: { + prefix_argument: expect.stringMatching('/foo-'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + ]), + }, + }) + .start(); - const result = await runner.makeRequest('get', '/mkdtemp'); - expect(result).toEqual('done'); - await runner.completed(); -}); + const result = await runner.makeRequest('get', '/mkdtemp'); + expect(result).toEqual('done'); + await runner.completed(); + }); -test('should create spans for fs operations that take target argument', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - transaction: { - transaction: 'GET /symlink', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: 'fs.symlink', - op: 'file', - status: 'ok', - data: { - target_argument: expect.stringMatching('/some-file-promisify.txt'), - path_argument: expect.stringMatching('/some-file-promisify.txt.symlink'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - expect.objectContaining({ - description: 'fs.symlink', - op: 'file', - status: 'ok', - data: { - target_argument: expect.stringMatching('/fixtures/some-file-promisify.txt'), - path_argument: expect.stringMatching('/fixtures/some-file-promisify.txt.symlink'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - expect.objectContaining({ - description: 'fs.symlink', - op: 'file', - status: 'ok', - data: { - target_argument: expect.stringMatching('/fixtures/some-file-promisify.txt'), - path_argument: expect.stringMatching('/fixtures/some-file-promisify.txt.symlink'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - ]), - }, - }) - .start(); + test('should create spans for fs symlink operations that take target argument', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /symlink', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'fs.symlink', + op: 'file', + status: 'ok', + data: { + target_argument: expect.stringMatching('/some-file-promisify.txt'), + path_argument: expect.stringMatching('/some-file-promisify.txt.symlink'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + expect.objectContaining({ + description: 'fs.symlink', + op: 'file', + status: 'ok', + data: { + target_argument: expect.stringMatching('/fixtures/some-file-promisify.txt'), + path_argument: expect.stringMatching('/fixtures/some-file-promisify.txt.symlink'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + expect.objectContaining({ + description: 'fs.symlink', + op: 'file', + status: 'ok', + data: { + target_argument: expect.stringMatching('/fixtures/some-file-promisify.txt'), + path_argument: expect.stringMatching('/fixtures/some-file-promisify.txt.symlink'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + ]), + }, + }) + .start(); - const result = await runner.makeRequest('get', '/symlink'); - expect(result).toEqual('done'); - await runner.completed(); -}); + const result = await runner.makeRequest('get', '/symlink'); + expect(result).toEqual('done'); + await runner.completed(); + }); -test('should create spans for fs.exists callback and promisified versions', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - transaction: { - transaction: 'GET /exists', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: 'fs.exists', - op: 'file', - status: 'ok', - data: { - path_argument: expect.stringMatching('/fixtures/some-file.txt'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - expect.objectContaining({ - description: 'fs.exists', - op: 'file', - status: 'ok', - data: { - path_argument: expect.stringMatching('/fixtures/some-file-promisify.txt'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - ]), + test('should create spans for fs.exists callback and promisified versions', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /exists', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'fs.exists', + op: 'file', + status: 'ok', + data: { + path_argument: expect.stringMatching('/fixtures/some-file.txt'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + expect.objectContaining({ + description: 'fs.exists', + op: 'file', + status: 'ok', + data: { + path_argument: expect.stringMatching('/fixtures/some-file-promisify.txt'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + ]), + }, + }) + .start(); + + const result = await runner.makeRequest('get', '/exists'); + expect(result).toEqual('done'); + await runner.completed(); + }); }, - }) - .start(); + { copyPaths: ['fixtures'] }, + ); + }); - const result = await runner.makeRequest('get', '/exists'); - expect(result).toEqual('done'); - await runner.completed(); -}); + describe('records file paths only', () => { + createCjsTests( + __dirname, + 'scenario.mjs', + 'instrument-record-paths-only.mjs', + (createRunner, test) => { + test('records file path but not error messages when only `recordFilePaths` is enabled', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /readFile-error', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'fs.readFile', + op: 'file', + status: 'internal_error', + // `path_argument` is recorded, but `fs_error` is NOT, since `recordErrorMessagesAsSpanAttributes` is off + data: { + path_argument: expect.stringMatching('/fixtures/some-file-that-doesnt-exist.txt'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + ]), + }, + }) + .start(); -test('records file path but not error messages when only `recordFilePaths` is enabled', async () => { - const runner = createRunner(__dirname, 'server-record-paths-only.ts') - .expect({ - transaction: { - transaction: 'GET /readFile-error', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: 'fs.readFile', - op: 'file', - status: 'internal_error', - // `path_argument` is recorded, but `fs_error` is NOT, since `recordErrorMessagesAsSpanAttributes` is off - data: { - path_argument: expect.stringMatching('/fixtures/some-file-that-doesnt-exist.txt'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - ]), + const result = await runner.makeRequest('get', '/readFile-error'); + expect(result).toEqual('done'); + await runner.completed(); + }); }, - }) - .start(); + { copyPaths: ['fixtures'] }, + ); + }); - const result = await runner.makeRequest('get', '/readFile-error'); - expect(result).toEqual('done'); - await runner.completed(); -}); + describe('records error messages only', () => { + createCjsTests( + __dirname, + 'scenario.mjs', + 'instrument-record-errors-only.mjs', + (createRunner, test) => { + test('records error messages but not file paths when only `recordErrorMessagesAsSpanAttributes` is enabled', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /readFile-error', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'fs.readFile', + op: 'file', + status: 'internal_error', + // `fs_error` is recorded, but `path_argument` is NOT, since `recordFilePaths` is off + data: { + fs_error: expect.stringMatching('ENOENT: no such file or directory,'), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + ]), + }, + }) + .start(); -test('records error messages but not file paths when only `recordErrorMessagesAsSpanAttributes` is enabled', async () => { - const runner = createRunner(__dirname, 'server-record-errors-only.ts') - .expect({ - transaction: { - transaction: 'GET /readFile-error', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: 'fs.readFile', - op: 'file', - status: 'internal_error', - // `fs_error` is recorded, but `path_argument` is NOT, since `recordFilePaths` is off - data: { - fs_error: expect.stringMatching('ENOENT: no such file or directory,'), - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - ]), - }, - }) - .start(); + const result = await runner.makeRequest('get', '/readFile-error'); + expect(result).toEqual('done'); + await runner.completed(); + }); - const result = await runner.makeRequest('get', '/readFile-error'); - expect(result).toEqual('done'); - await runner.completed(); -}); + test('does not record file paths on successful operations when only `recordErrorMessagesAsSpanAttributes` is enabled', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /readFile', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'fs.readFile', + op: 'file', + status: 'ok', + // Neither `path_argument` nor `fs_error` are recorded + data: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', + }, + }), + ]), + }, + }) + .start(); -test('does not record file paths on successful operations when only `recordErrorMessagesAsSpanAttributes` is enabled', async () => { - const runner = createRunner(__dirname, 'server-record-errors-only.ts') - .expect({ - transaction: { - transaction: 'GET /readFile', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: 'fs.readFile', - op: 'file', - status: 'ok', - // Neither `path_argument` nor `fs_error` are recorded - data: { - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'file', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.file.fs', - }, - }), - ]), + const result = await runner.makeRequest('get', '/readFile'); + expect(result).toEqual('done'); + await runner.completed(); + }); }, - }) - .start(); - - const result = await runner.makeRequest('get', '/readFile'); - expect(result).toEqual('done'); - await runner.completed(); + { copyPaths: ['fixtures'] }, + ); + }); });