Skip to content

Commit 98979d8

Browse files
authored
test(node): New Node integration test runner (#10117)
Adds a new Node integration test runner that runs the scenario in its own Node process and reads the envelope back from stdout. I have converted all the `Anr` and `LocalVariables` tests to use this new format. ## How to use First, in your test scenario, you need to set the transport to the `loggingTransport` from `@sentry-internal/node-integration-tests`. This will cause envelopes to be logged to stdout as a single line of JSON: ```ts import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ dsn: 'https://[email protected]/1337', transport: loggingTransport, }); ``` Then in your test, use the `createRunner` function to create your test. The `createRunner` args are the path segments to the scenario. If you pass a typescript entry file, `-r ts-node/register` gets added to the node args. Every `expect` call adds an expected envelope item. Currently you can expect `event`, `transaction` and `session` but it's easy to add extra type-safe expectations. You must add an `expect` for every envelope **item** that will be sent and they must be in the correct order or an error is thrown and the test fails. The expect `event`/`transaction`/`session` can be a callback or an object that will be tested against the received envelope. If you don't care about specific types of envelope item, you can ignore them: ```ts createRunner(__dirname, 'basic.js') .ignore('session', 'sessions') .expect(//... ``` If you pass `done` to `start`, `done` will be called when all the envelope items have matched the expected and `done` will be passed any errors that occur which ends the test without waiting for the timeout. ## Example Here is an Anr test that ensures we get the expected session envelope followed by the expected event envelope: ```ts import { assertSentryEvent, assertSentrySession, createRunner } from '../../utils/runner'; const EXPECTED_ANR_EVENT = { // Ensure we have context contexts: { trace: { span_id: expect.any(String), trace_id: expect.any(String), }, device: { arch: expect.any(String), }, app: { app_start_time: expect.any(String), }, os: { name: expect.any(String), }, culture: { timezone: expect.any(String), }, }, // and an exception that is our ANR exception: { values: [ { type: 'ApplicationNotResponding', value: 'Application Not Responding for at least 200 ms', mechanism: { type: 'ANR' }, stacktrace: { frames: expect.arrayContaining([ { colno: expect.any(Number), lineno: expect.any(Number), filename: expect.any(String), function: '?', in_app: true, }, { colno: expect.any(Number), lineno: expect.any(Number), filename: expect.any(String), function: 'longWork', in_app: true, }, ]), }, }, ], }, }; test('Anr with session', done => { createRunner(__dirname, 'basic-session.js') .expect({ session: { status: 'abnormal', abnormal_mechanism: 'anr_foreground' } }) .expect({ event: EXPECTED_ANR_EVENT }) .start(done); }); ``` It's also possible to pass flags to node via the `withFlags()` function: ```ts test('With --inspect',done => { createRunner(__dirname, 'basic.mjs') .withFlags('--inspect') .expect({ event: EXPECTED_ANR_EVENT }) .start(done); }); ```
1 parent 45ef67e commit 98979d8

File tree

23 files changed

+535
-381
lines changed

23 files changed

+535
-381
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ env:
3535

3636
# packages/utils/cjs and packages/utils/esm: Symlinks to the folders inside of `build`, needed for tests
3737
CACHED_BUILD_PATHS: |
38+
${{ github.workspace }}/dev-packages/*/build
3839
${{ github.workspace }}/packages/*/build
3940
${{ github.workspace }}/packages/ember/*.d.ts
4041
${{ github.workspace }}/packages/gatsby/*.d.ts

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module.exports = {
66
extends: ['../../.eslintrc.js'],
77
overrides: [
88
{
9-
files: ['utils/**/*.ts'],
9+
files: ['utils/**/*.ts', 'src/**/*.ts'],
1010
parserOptions: {
1111
project: ['tsconfig.json'],
1212
sourceType: 'module',

dev-packages/node-integration-tests/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66
"node": ">=10"
77
},
88
"private": true,
9+
"main": "build/cjs/index.js",
10+
"module": "build/esm/index.js",
11+
"types": "build/types/src/index.d.ts",
912
"scripts": {
13+
"build": "run-s build:transpile build:types",
14+
"build:dev": "yarn build",
15+
"build:transpile": "rollup -c rollup.npm.config.mjs",
16+
"build:types": "tsc -p tsconfig.types.json",
1017
"clean": "rimraf -g **/node_modules",
1118
"prisma:init": "(cd suites/tracing/prisma-orm && ts-node ./setup.ts)",
1219
"prisma:init:new": "(cd suites/tracing-new/prisma-orm && ts-node ./setup.ts)",
@@ -15,12 +22,14 @@
1522
"type-check": "tsc",
1623
"pretest": "run-s --silent prisma:init prisma:init:new",
1724
"test": "ts-node ./utils/run-tests.ts",
25+
"jest": "jest --config ./jest.config.js",
1826
"test:watch": "yarn test --watch"
1927
},
2028
"dependencies": {
2129
"@prisma/client": "3.15.2",
2230
"@sentry/node": "7.93.0",
2331
"@sentry/tracing": "7.93.0",
32+
"@sentry/types": "7.93.0",
2433
"@types/mongodb": "^3.6.20",
2534
"@types/mysql": "^2.15.21",
2635
"@types/pg": "^8.6.5",
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils';
2+
3+
export default makeNPMConfigVariants(makeBaseNPMConfig());
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { AddressInfo } from 'net';
2+
import type { BaseTransportOptions, Envelope, Transport, TransportMakeRequestResponse } from '@sentry/types';
3+
import type { Express } from 'express';
4+
5+
/**
6+
* Debug logging transport
7+
*/
8+
export function loggingTransport(_options: BaseTransportOptions): Transport {
9+
return {
10+
send(request: Envelope): Promise<void | TransportMakeRequestResponse> {
11+
// eslint-disable-next-line no-console
12+
console.log(JSON.stringify(request));
13+
return Promise.resolve({ statusCode: 200 });
14+
},
15+
flush(): PromiseLike<boolean> {
16+
return Promise.resolve(true);
17+
},
18+
};
19+
}
20+
21+
/**
22+
* Starts an express server and sends the port to the runner
23+
*/
24+
export function startExpressServerAndSendPortToRunner(app: Express): void {
25+
const server = app.listen(0, () => {
26+
const address = server.address() as AddressInfo;
27+
28+
// eslint-disable-next-line no-console
29+
console.log(`{"port":${address.port}}`);
30+
});
31+
}

dev-packages/node-integration-tests/suites/anr/basic-session.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ Sentry.init({
1111
dsn: 'https://[email protected]/1337',
1212
release: '1.0',
1313
debug: true,
14-
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })],
14+
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 100 })],
1515
});
1616

1717
function longWork() {
18-
for (let i = 0; i < 100; i++) {
18+
for (let i = 0; i < 20; i++) {
1919
const salt = crypto.randomBytes(128).toString('base64');
2020
// eslint-disable-next-line no-unused-vars
2121
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ Sentry.init({
1212
release: '1.0',
1313
debug: true,
1414
autoSessionTracking: false,
15-
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })],
15+
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 100 })],
1616
});
1717

1818
function longWork() {
19-
for (let i = 0; i < 100; i++) {
19+
for (let i = 0; i < 20; i++) {
2020
const salt = crypto.randomBytes(128).toString('base64');
2121
// eslint-disable-next-line no-unused-vars
2222
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ Sentry.init({
1212
release: '1.0',
1313
debug: true,
1414
autoSessionTracking: false,
15-
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })],
15+
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 100 })],
1616
});
1717

1818
function longWork() {
19-
for (let i = 0; i < 100; i++) {
19+
for (let i = 0; i < 20; i++) {
2020
const salt = crypto.randomBytes(128).toString('base64');
2121
// eslint-disable-next-line no-unused-vars
2222
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');

dev-packages/node-integration-tests/suites/anr/forked.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ Sentry.init({
1212
release: '1.0',
1313
debug: true,
1414
autoSessionTracking: false,
15-
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })],
15+
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 100 })],
1616
});
1717

1818
function longWork() {
19-
for (let i = 0; i < 100; i++) {
19+
for (let i = 0; i < 20; i++) {
2020
const salt = crypto.randomBytes(128).toString('base64');
2121
// eslint-disable-next-line no-unused-vars
2222
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');

dev-packages/node-integration-tests/suites/anr/legacy.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ Sentry.init({
1515
});
1616

1717
// eslint-disable-next-line deprecation/deprecation
18-
Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200 }).then(() => {
18+
Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 100 }).then(() => {
1919
function longWork() {
20-
for (let i = 0; i < 100; i++) {
20+
for (let i = 0; i < 20; i++) {
2121
const salt = crypto.randomBytes(128).toString('base64');
2222
// eslint-disable-next-line no-unused-vars
2323
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');

0 commit comments

Comments
 (0)