Skip to content

Commit 2e164e1

Browse files
authored
test(node): Add utility to test esm & cjs instrumentation (#16159)
Today, we do not have a lot of esm-specific node integration tests. Our tests make it possible to test ESM, but we only use it very rarely, sticking to cjs tests across the suite mostly. This means that we have a bunch of gaps around our ESM support. This PR introduces a new test utility to make it easier to test stuff in ESM and CJS: ```js createEsmAndCjsTests( __dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { test('it works when importing the http module', async () => { const runner = createRunner(); // normal test as before }); }); ``` This has a few limitations based on how this works - we can make this more robust in the future, but for now it should be "OK": 1. It requires a .mjs based instrument file as well as an .mjs based scenario 2. No relative imports are supported (all content must be in these two files) 3. It simply regex replaces the esm imports with require for the CJS tests. not perfect, but it kind of works For tests that are known to fail on e.g. esm or cjs, you can configure `failsOnEsm: true`. In this case, it will fail if the test _does not fail_ (by using `test.fails()` to ensure test failure). This way we can ensure we find out if stuff starts to fail etc. To make this work, I had to re-write the test runner code a bit, because it had issues with vitest unhandled rejection handling. Due to the way we handled test completion with a promise, `test.fails` was not working as expected, because the test indeed succeeded when it failed, but the overall test run still failed because an unhandled rejection bubbled up. Now, this should work as expected... I re-wrote a few tests already to show how it works, plus added a new test that shows an ESM test failure when importing http module (😭 )
1 parent 2130d35 commit 2e164e1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+818
-703
lines changed

dev-packages/node-integration-tests/.eslintrc.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ module.exports = {
1212
},
1313
},
1414
{
15-
files: ['suites/**/*.ts'],
15+
files: ['suites/**/*.ts', 'suites/**/*.mjs'],
1616
parserOptions: {
1717
project: ['tsconfig.test.json'],
1818
sourceType: 'module',
19+
ecmaVersion: 'latest',
1920
},
2021
rules: {
2122
'@typescript-eslint/typedef': 'off',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
suites/**/tmp_*

dev-packages/node-integration-tests/suites/anr/app-path.mjs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1+
import * as Sentry from '@sentry/node';
12
import * as assert from 'assert';
23
import * as crypto from 'crypto';
34
import * as path from 'path';
45
import * as url from 'url';
56

6-
import * as Sentry from '@sentry/node';
7-
87
global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' };
98

109
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));

dev-packages/node-integration-tests/suites/anr/basic-multiple.mjs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1+
import * as Sentry from '@sentry/node';
12
import * as assert from 'assert';
23
import * as crypto from 'crypto';
34

4-
import * as Sentry from '@sentry/node';
5-
65
global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' };
76

87
setTimeout(() => {

dev-packages/node-integration-tests/suites/anr/basic.mjs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1+
import * as Sentry from '@sentry/node';
12
import * as assert from 'assert';
23
import * as crypto from 'crypto';
34

4-
import * as Sentry from '@sentry/node';
5-
65
global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' };
76

87
setTimeout(() => {

dev-packages/node-integration-tests/suites/anr/indefinite.mjs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1+
import * as Sentry from '@sentry/node';
12
import * as assert from 'assert';
23
import * as crypto from 'crypto';
34

4-
import * as Sentry from '@sentry/node';
5-
65
setTimeout(() => {
76
process.exit();
87
}, 10000);

dev-packages/node-integration-tests/suites/anr/isolated.mjs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1+
import * as Sentry from '@sentry/node';
12
import * as assert from 'assert';
23
import * as crypto from 'crypto';
34

4-
import * as Sentry from '@sentry/node';
5-
65
setTimeout(() => {
76
process.exit();
87
}, 10000);

dev-packages/node-integration-tests/suites/anr/test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ const ANR_EVENT_WITH_DEBUG_META: Event = {
107107
},
108108
};
109109

110-
describe('should report ANR when event loop blocked', { timeout: 60_000 }, () => {
110+
describe('should report ANR when event loop blocked', { timeout: 90_000 }, () => {
111111
afterAll(() => {
112112
cleanupChildProcesses();
113113
});
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import * as Sentry from '@sentry/node';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
13
import { spawn } from 'child_process';
24
import { join } from 'path';
3-
import { loggingTransport } from '@sentry-internal/node-integration-tests';
4-
import * as Sentry from '@sentry/node';
55
import { Worker } from 'worker_threads';
66

77
const __dirname = new URL('.', import.meta.url).pathname;
@@ -13,16 +13,18 @@ Sentry.init({
1313
transport: loggingTransport,
1414
});
1515

16-
await new Promise(resolve => {
17-
const child = spawn('sleep', ['a']);
18-
child.on('error', resolve);
19-
child.on('exit', resolve);
20-
});
16+
(async () => {
17+
await new Promise(resolve => {
18+
const child = spawn('sleep', ['a']);
19+
child.on('error', resolve);
20+
child.on('exit', resolve);
21+
});
2122

22-
await new Promise(resolve => {
23-
const worker = new Worker(join(__dirname, 'worker.mjs'));
24-
worker.on('error', resolve);
25-
worker.on('exit', resolve);
26-
});
23+
await new Promise(resolve => {
24+
const worker = new Worker(join(__dirname, 'worker.mjs'));
25+
worker.on('error', resolve);
26+
worker.on('exit', resolve);
27+
});
2728

28-
throw new Error('This is a test error');
29+
throw new Error('This is a test error');
30+
})();

dev-packages/node-integration-tests/suites/child-process/fork.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ Sentry.init({
1010
transport: loggingTransport,
1111
});
1212

13-
// eslint-disable-next-line no-unused-vars
14-
const _child = fork(path.join(__dirname, 'child.mjs'));
13+
fork(path.join(__dirname, 'child.mjs'));
1514

1615
setTimeout(() => {
1716
throw new Error('Exiting main process');

dev-packages/node-integration-tests/suites/child-process/fork.mjs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import * as Sentry from '@sentry/node';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
13
import { fork } from 'child_process';
24
import * as path from 'path';
3-
import { loggingTransport } from '@sentry-internal/node-integration-tests';
4-
import * as Sentry from '@sentry/node';
55

66
const __dirname = new URL('.', import.meta.url).pathname;
77

@@ -12,7 +12,7 @@ Sentry.init({
1212
transport: loggingTransport,
1313
});
1414

15-
const _child = fork(path.join(__dirname, 'child.mjs'));
15+
fork(path.join(__dirname, 'child.mjs'));
1616

1717
setTimeout(() => {
1818
throw new Error('Exiting main process');

dev-packages/node-integration-tests/suites/child-process/worker.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ Sentry.init({
1010
transport: loggingTransport,
1111
});
1212

13-
// eslint-disable-next-line no-unused-vars
14-
const _worker = new Worker(path.join(__dirname, 'child.js'));
13+
new Worker(path.join(__dirname, 'child.js'));
1514

1615
setTimeout(() => {
1716
process.exit();

dev-packages/node-integration-tests/suites/child-process/worker.mjs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import * as path from 'path';
2-
import { loggingTransport } from '@sentry-internal/node-integration-tests';
31
import * as Sentry from '@sentry/node';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
3+
import * as path from 'path';
44
import { Worker } from 'worker_threads';
55

66
const __dirname = new URL('.', import.meta.url).pathname;
@@ -12,7 +12,7 @@ Sentry.init({
1212
transport: loggingTransport,
1313
});
1414

15-
const _worker = new Worker(path.join(__dirname, 'child.mjs'));
15+
new Worker(path.join(__dirname, 'child.mjs'));
1616

1717
setTimeout(() => {
1818
process.exit();

dev-packages/node-integration-tests/suites/contextLines/filename-with-spaces/instrument.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { loggingTransport } from '@sentry-internal/node-integration-tests';
21
import * as Sentry from '@sentry/node';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
33

44
Sentry.init({
55
dsn: 'https://public@dsn.ingest.sentry.io/1337',

dev-packages/node-integration-tests/suites/contextLines/filename-with-spaces/test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ describe('ContextLines integration in ESM', () => {
88
const instrumentPath = join(__dirname, 'instrument.mjs');
99

1010
await createRunner(__dirname, 'scenario with space.mjs')
11-
.withFlags('--import', instrumentPath)
11+
.withInstrument(instrumentPath)
1212
.expect({
1313
event: {
1414
exception: {

dev-packages/node-integration-tests/suites/esm/import-in-the-middle/app.mjs

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { loggingTransport } from '@sentry-internal/node-integration-tests';
21
import * as Sentry from '@sentry/node';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
33
import * as iitm from 'import-in-the-middle';
44

55
new iitm.Hook((_, name) => {
@@ -14,6 +14,8 @@ Sentry.init({
1414
transport: loggingTransport,
1515
});
1616

17-
await import('./sub-module.mjs');
18-
await import('http');
19-
await import('os');
17+
(async () => {
18+
await import('./sub-module.mjs');
19+
await import('http');
20+
await import('os');
21+
})();

dev-packages/node-integration-tests/suites/esm/modules-integration/app.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { loggingTransport } from '@sentry-internal/node-integration-tests';
21
import * as Sentry from '@sentry/node';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
33

44
Sentry.init({
55
dsn: 'https://public@dsn.ingest.sentry.io/1337',

dev-packages/node-integration-tests/suites/esm/warn-esm/server.mjs

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { loggingTransport } from '@sentry-internal/node-integration-tests';
21
import * as Sentry from '@sentry/node';
2+
import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests';
3+
import express from 'express';
34

45
Sentry.init({
56
dsn: 'https://public@dsn.ingest.sentry.io/1337',
67
release: '1.0',
78
transport: loggingTransport,
89
});
910

10-
import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests';
11-
import express from 'express';
12-
1311
const app = express();
1412

1513
app.get('/test/success', (req, res) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as Sentry from '@sentry/node';
2+
import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests';
3+
import express from 'express';
4+
import http from 'http';
5+
6+
const app = express();
7+
8+
app.get('/test', (_req, res) => {
9+
http.get(`http://localhost:${app.port}/test2`, httpRes => {
10+
httpRes.on('data', () => {
11+
setTimeout(() => {
12+
res.send({ response: 'response 1' });
13+
}, 200);
14+
});
15+
});
16+
});
17+
18+
app.get('/test2', (_req, res) => {
19+
res.send({ response: 'response 2' });
20+
});
21+
22+
app.get('/test3', (_req, res) => {
23+
res.send({ response: 'response 3' });
24+
});
25+
26+
Sentry.setupExpressErrorHandler(app);
27+
28+
startExpressServerAndSendPortToRunner(app);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { afterAll, describe } from 'vitest';
2+
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';
3+
4+
describe('express with http import', () => {
5+
afterAll(() => {
6+
cleanupChildProcesses();
7+
});
8+
9+
createEsmAndCjsTests(
10+
__dirname,
11+
'scenario.mjs',
12+
'instrument.mjs',
13+
(createRunner, test) => {
14+
test('it works when importing the http module', async () => {
15+
const runner = createRunner()
16+
.expect({
17+
transaction: {
18+
transaction: 'GET /test2',
19+
},
20+
})
21+
.expect({
22+
transaction: {
23+
transaction: 'GET /test',
24+
},
25+
})
26+
.expect({
27+
transaction: {
28+
transaction: 'GET /test3',
29+
},
30+
})
31+
.start();
32+
await runner.makeRequest('get', '/test');
33+
await runner.makeRequest('get', '/test3');
34+
await runner.completed();
35+
});
36+
// TODO: This is failing on ESM because importing http is triggering the http spans twice :(
37+
// We need to fix this!
38+
},
39+
{ failsOnEsm: true },
40+
);
41+
});
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { register } from 'node:module';
22

3-
const hookScript = Buffer.from(`
4-
5-
`);
6-
73
register(
84
new URL(`data:application/javascript,
95
export async function resolve(specifier, context, nextResolve) {
@@ -16,6 +12,8 @@ export async function resolve(specifier, context, nextResolve) {
1612
import.meta.url,
1713
);
1814

19-
const Sentry = await import('@sentry/node');
15+
(async () => {
16+
const Sentry = await import('@sentry/node');
2017

21-
Sentry.init({});
18+
Sentry.init({});
19+
})();

dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable no-unused-vars */
2-
import { loggingTransport } from '@sentry-internal/node-integration-tests';
32
import * as Sentry from '@sentry/node';
3+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
44

55
Sentry.init({
66
dsn: 'https://public@dsn.ingest.sentry.io/1337',

dev-packages/node-integration-tests/suites/tracing/ai/scenario.js

-58
This file was deleted.

0 commit comments

Comments
 (0)