Skip to content

Commit aa76951

Browse files
authored
test: Add tests to demonstrate withScope data loss for uncaught errors (#11204)
This PR adds integration tests (browser and node) to demonstrate the current behavior around loosing scope data when a thrown error is caught by global handlers "outside" the lifetime of the throw origin's scope. This occurs specifically within the `withScope` API where the scope is popped if an error is caught but then the error is rethrown to be caught by handlers down the line.
1 parent 3cc0344 commit aa76951

File tree

4 files changed

+160
-0
lines changed
  • dev-packages

4 files changed

+160
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Sentry.setTag('global', 'tag');
2+
setTimeout(() => {
3+
Sentry.withScope(scope => {
4+
scope.setTag('local', 'tag');
5+
throw new Error('test error');
6+
});
7+
}, 10);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../../utils/fixtures';
5+
import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers';
6+
7+
/**
8+
* Why does this test exist?
9+
*
10+
* We recently discovered that errors caught by global handlers will potentially loose scope data from the active scope
11+
* where the error was thrown in. The simple example in this test (see subject.ts) demonstrates this behavior (in a
12+
* browser environment but the same behavior applies to the server; see the test there).
13+
*
14+
* This test nevertheless covers the behavior so that we're aware.
15+
*/
16+
sentryTest(
17+
'withScope scope is NOT applied to thrown error caught by global handler',
18+
async ({ getLocalTestPath, page }) => {
19+
const url = await getLocalTestPath({ testDir: __dirname });
20+
21+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
22+
23+
const ex = eventData.exception?.values ? eventData.exception.values[0] : undefined;
24+
25+
// This tag is missing :(
26+
expect(eventData.tags?.local).toBeUndefined();
27+
28+
expect(eventData.tags).toMatchObject({
29+
global: 'tag',
30+
});
31+
expect(ex?.value).toBe('test error');
32+
},
33+
);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests';
2+
import * as Sentry from '@sentry/node-experimental';
3+
import express from 'express';
4+
5+
const app = express();
6+
7+
Sentry.init({
8+
dsn: 'https://[email protected]/1337',
9+
release: '1.0',
10+
transport: loggingTransport,
11+
});
12+
13+
app.use(Sentry.Handlers.requestHandler());
14+
15+
Sentry.setTag('global', 'tag');
16+
17+
app.get('/test/withScope', () => {
18+
Sentry.withScope(scope => {
19+
scope.setTag('local', 'tag');
20+
throw new Error('test_error');
21+
});
22+
});
23+
24+
app.get('/test/isolationScope', () => {
25+
Sentry.getIsolationScope().setTag('isolation-scope', 'tag');
26+
throw new Error('isolation_test_error');
27+
});
28+
29+
app.use(Sentry.Handlers.errorHandler());
30+
31+
startExpressServerAndSendPortToRunner(app);
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';
2+
3+
afterAll(() => {
4+
cleanupChildProcesses();
5+
});
6+
7+
/**
8+
* Why does this test exist?
9+
*
10+
* We recently discovered that errors caught by global handlers will potentially loose scope data from the active scope
11+
* where the error was originally thrown in. The simple example in this test (see subject.ts) demonstrates this behavior
12+
* (in a Node environment but the same behavior applies to the browser; see the test there).
13+
*
14+
* This test nevertheless covers the behavior so that we're aware.
15+
*/
16+
test('withScope scope is NOT applied to thrown error caught by global handler', done => {
17+
const runner = createRunner(__dirname, 'server.ts')
18+
.ignore('session', 'sessions')
19+
.expect({
20+
event: {
21+
exception: {
22+
values: [
23+
{
24+
mechanism: {
25+
type: 'middleware',
26+
handled: false,
27+
},
28+
type: 'Error',
29+
value: 'test_error',
30+
stacktrace: {
31+
frames: expect.arrayContaining([
32+
expect.objectContaining({
33+
function: expect.any(String),
34+
lineno: expect.any(Number),
35+
colno: expect.any(Number),
36+
}),
37+
]),
38+
},
39+
},
40+
],
41+
},
42+
// 'local' tag is not applied to the event
43+
tags: expect.not.objectContaining({ local: expect.anything() }),
44+
},
45+
})
46+
.start(done);
47+
48+
expect(() => runner.makeRequest('get', '/test/withScope')).rejects.toThrow();
49+
});
50+
51+
/**
52+
* This test shows that the isolation scope set tags are applied correctly to the error.
53+
*/
54+
test('isolation scope is applied to thrown error caught by global handler', done => {
55+
const runner = createRunner(__dirname, 'server.ts')
56+
.ignore('session', 'sessions')
57+
.expect({
58+
event: {
59+
exception: {
60+
values: [
61+
{
62+
mechanism: {
63+
type: 'middleware',
64+
handled: false,
65+
},
66+
type: 'Error',
67+
value: 'isolation_test_error',
68+
stacktrace: {
69+
frames: expect.arrayContaining([
70+
expect.objectContaining({
71+
function: expect.any(String),
72+
lineno: expect.any(Number),
73+
colno: expect.any(Number),
74+
}),
75+
]),
76+
},
77+
},
78+
],
79+
},
80+
tags: {
81+
global: 'tag',
82+
'isolation-scope': 'tag',
83+
},
84+
},
85+
})
86+
.start(done);
87+
88+
expect(() => runner.makeRequest('get', '/test/isolationScope')).rejects.toThrow();
89+
});

0 commit comments

Comments
 (0)