|
1 | | -import type { Debugger, InspectorNotification } from 'inspector'; |
2 | | - |
3 | | -import { NodeClient, defaultStackParser } from '../../src'; |
4 | 1 | import { createRateLimiter } from '../../src/integrations/local-variables/common'; |
5 | | -import type { FrameVariables } from '../../src/integrations/local-variables/common'; |
6 | | -import type { DebugSession } from '../../src/integrations/local-variables/local-variables-sync'; |
7 | | -import { LocalVariablesSync, createCallbackList } from '../../src/integrations/local-variables/local-variables-sync'; |
| 2 | +import { createCallbackList } from '../../src/integrations/local-variables/local-variables-sync'; |
8 | 3 | import { NODE_VERSION } from '../../src/nodeVersion'; |
9 | | -import { getDefaultNodeClientOptions } from '../../test/helper/node-client-options'; |
10 | 4 |
|
11 | 5 | jest.setTimeout(20_000); |
12 | 6 |
|
13 | 7 | const describeIf = (condition: boolean) => (condition ? describe : describe.skip); |
14 | 8 |
|
15 | | -interface ThrowOn { |
16 | | - configureAndConnect?: boolean; |
17 | | - getLocalVariables?: boolean; |
18 | | -} |
19 | | - |
20 | | -class MockDebugSession implements DebugSession { |
21 | | - private _onPause?: (message: InspectorNotification<Debugger.PausedEventDataType>, callback: () => void) => void; |
22 | | - |
23 | | - constructor(private readonly _vars: Record<string, Record<string, unknown>>, private readonly _throwOn?: ThrowOn) {} |
24 | | - |
25 | | - public configureAndConnect( |
26 | | - onPause: (message: InspectorNotification<Debugger.PausedEventDataType>, callback: () => void) => void, |
27 | | - _captureAll: boolean, |
28 | | - ): void { |
29 | | - if (this._throwOn?.configureAndConnect) { |
30 | | - throw new Error('configureAndConnect should not be called'); |
31 | | - } |
32 | | - |
33 | | - this._onPause = onPause; |
34 | | - } |
35 | | - |
36 | | - public setPauseOnExceptions(_: boolean): void {} |
37 | | - |
38 | | - public getLocalVariables(objectId: string, callback: (vars: Record<string, unknown>) => void): void { |
39 | | - if (this._throwOn?.getLocalVariables) { |
40 | | - throw new Error('getLocalVariables should not be called'); |
41 | | - } |
42 | | - |
43 | | - callback(this._vars[objectId]); |
44 | | - } |
45 | | - |
46 | | - public runPause(message: InspectorNotification<Debugger.PausedEventDataType>): Promise<void> { |
47 | | - return new Promise(resolve => { |
48 | | - this._onPause?.(message, resolve); |
49 | | - }); |
50 | | - } |
51 | | -} |
52 | | - |
53 | | -interface LocalVariablesPrivate { |
54 | | - _getCachedFramesCount(): number; |
55 | | - _getFirstCachedFrame(): FrameVariables[] | undefined; |
56 | | -} |
57 | | - |
58 | | -const exceptionEvent = { |
59 | | - method: 'Debugger.paused', |
60 | | - params: { |
61 | | - reason: 'exception', |
62 | | - data: { |
63 | | - description: |
64 | | - 'Error: Some error\n' + |
65 | | - ' at two (/dist/javascript/src/main.js:23:9)\n' + |
66 | | - ' at one (/dist/javascript/src/main.js:19:3)\n' + |
67 | | - ' at Timeout._onTimeout (/dist/javascript/src/main.js:40:5)\n' + |
68 | | - ' at listOnTimeout (node:internal/timers:559:17)\n' + |
69 | | - ' at process.processTimers (node:internal/timers:502:7)', |
70 | | - }, |
71 | | - callFrames: [ |
72 | | - { |
73 | | - callFrameId: '-6224981551105448869.1.0', |
74 | | - functionName: 'two', |
75 | | - location: { scriptId: '134', lineNumber: 22 }, |
76 | | - url: '', |
77 | | - scopeChain: [ |
78 | | - { |
79 | | - type: 'local', |
80 | | - object: { |
81 | | - type: 'object', |
82 | | - className: 'Object', |
83 | | - objectId: '-6224981551105448869.1.2', |
84 | | - }, |
85 | | - name: 'two', |
86 | | - }, |
87 | | - ], |
88 | | - this: { |
89 | | - type: 'object', |
90 | | - className: 'global', |
91 | | - }, |
92 | | - }, |
93 | | - { |
94 | | - callFrameId: '-6224981551105448869.1.1', |
95 | | - functionName: 'one', |
96 | | - location: { scriptId: '134', lineNumber: 18 }, |
97 | | - url: '', |
98 | | - scopeChain: [ |
99 | | - { |
100 | | - type: 'local', |
101 | | - object: { |
102 | | - type: 'object', |
103 | | - className: 'Object', |
104 | | - objectId: '-6224981551105448869.1.6', |
105 | | - }, |
106 | | - name: 'one', |
107 | | - }, |
108 | | - ], |
109 | | - this: { |
110 | | - type: 'object', |
111 | | - className: 'global', |
112 | | - }, |
113 | | - }, |
114 | | - ], |
115 | | - }, |
116 | | -}; |
117 | | - |
118 | | -const exceptionEvent100Frames = { |
119 | | - method: 'Debugger.paused', |
120 | | - params: { |
121 | | - reason: 'exception', |
122 | | - data: { |
123 | | - description: |
124 | | - 'Error: Some error\n' + |
125 | | - ' at two (/dist/javascript/src/main.js:23:9)\n' + |
126 | | - ' at one (/dist/javascript/src/main.js:19:3)\n' + |
127 | | - ' at Timeout._onTimeout (/dist/javascript/src/main.js:40:5)\n' + |
128 | | - ' at listOnTimeout (node:internal/timers:559:17)\n' + |
129 | | - ' at process.processTimers (node:internal/timers:502:7)', |
130 | | - }, |
131 | | - callFrames: new Array(100).fill({ |
132 | | - callFrameId: '-6224981551105448869.1.0', |
133 | | - functionName: 'two', |
134 | | - location: { scriptId: '134', lineNumber: 22 }, |
135 | | - url: '', |
136 | | - scopeChain: [ |
137 | | - { |
138 | | - type: 'local', |
139 | | - object: { |
140 | | - type: 'object', |
141 | | - className: 'Object', |
142 | | - objectId: '-6224981551105448869.1.2', |
143 | | - }, |
144 | | - name: 'two', |
145 | | - }, |
146 | | - ], |
147 | | - this: { |
148 | | - type: 'object', |
149 | | - className: 'global', |
150 | | - }, |
151 | | - }), |
152 | | - }, |
153 | | -}; |
154 | | - |
155 | 9 | describeIf(NODE_VERSION.major >= 18)('LocalVariables', () => { |
156 | | - it('Adds local variables to stack frames', async () => { |
157 | | - const session = new MockDebugSession({ |
158 | | - '-6224981551105448869.1.2': { name: 'tim' }, |
159 | | - '-6224981551105448869.1.6': { arr: [1, 2, 3] }, |
160 | | - }); |
161 | | - const localVariables = new LocalVariablesSync({}, session); |
162 | | - const options = getDefaultNodeClientOptions({ |
163 | | - stackParser: defaultStackParser, |
164 | | - includeLocalVariables: true, |
165 | | - integrations: [], |
166 | | - }); |
167 | | - |
168 | | - const client = new NodeClient(options); |
169 | | - client.addIntegration(localVariables); |
170 | | - |
171 | | - const eventProcessors = client['_eventProcessors']; |
172 | | - const eventProcessor = eventProcessors.find(processor => processor.id === 'LocalVariables'); |
173 | | - |
174 | | - expect(eventProcessor).toBeDefined(); |
175 | | - |
176 | | - await session.runPause(exceptionEvent); |
177 | | - |
178 | | - expect((localVariables as unknown as LocalVariablesPrivate)._getCachedFramesCount()).toBe(1); |
179 | | - |
180 | | - const frames = (localVariables as unknown as LocalVariablesPrivate)._getFirstCachedFrame(); |
181 | | - |
182 | | - expect(frames).toBeDefined(); |
183 | | - |
184 | | - const vars = frames as FrameVariables[]; |
185 | | - |
186 | | - expect(vars).toEqual([ |
187 | | - { function: 'two', vars: { name: 'tim' } }, |
188 | | - { function: 'one', vars: { arr: [1, 2, 3] } }, |
189 | | - ]); |
190 | | - |
191 | | - const event = await eventProcessor!( |
192 | | - { |
193 | | - event_id: '9cbf882ade9a415986632ac4e16918eb', |
194 | | - platform: 'node', |
195 | | - timestamp: 1671113680.306, |
196 | | - level: 'fatal', |
197 | | - exception: { |
198 | | - values: [ |
199 | | - { |
200 | | - type: 'Error', |
201 | | - value: 'Some error', |
202 | | - stacktrace: { |
203 | | - frames: [ |
204 | | - { |
205 | | - function: 'process.processTimers', |
206 | | - lineno: 502, |
207 | | - colno: 7, |
208 | | - in_app: false, |
209 | | - }, |
210 | | - { |
211 | | - function: 'listOnTimeout', |
212 | | - lineno: 559, |
213 | | - colno: 17, |
214 | | - in_app: false, |
215 | | - }, |
216 | | - { |
217 | | - function: 'Timeout._onTimeout', |
218 | | - lineno: 40, |
219 | | - colno: 5, |
220 | | - in_app: true, |
221 | | - }, |
222 | | - { |
223 | | - function: 'one', |
224 | | - lineno: 19, |
225 | | - colno: 3, |
226 | | - in_app: true, |
227 | | - }, |
228 | | - { |
229 | | - function: 'two', |
230 | | - lineno: 23, |
231 | | - colno: 9, |
232 | | - in_app: true, |
233 | | - }, |
234 | | - ], |
235 | | - }, |
236 | | - mechanism: { type: 'generic', handled: true }, |
237 | | - }, |
238 | | - ], |
239 | | - }, |
240 | | - }, |
241 | | - {}, |
242 | | - ); |
243 | | - |
244 | | - expect(event?.exception?.values?.[0].stacktrace?.frames?.[3]?.vars).toEqual({ arr: [1, 2, 3] }); |
245 | | - expect(event?.exception?.values?.[0].stacktrace?.frames?.[4]?.vars).toEqual({ name: 'tim' }); |
246 | | - |
247 | | - expect((localVariables as unknown as LocalVariablesPrivate)._getCachedFramesCount()).toBe(0); |
248 | | - }); |
249 | | - |
250 | | - it('Only considers the first 5 frames', async () => { |
251 | | - const session = new MockDebugSession({}); |
252 | | - const localVariables = new LocalVariablesSync({}, session); |
253 | | - const options = getDefaultNodeClientOptions({ |
254 | | - stackParser: defaultStackParser, |
255 | | - includeLocalVariables: true, |
256 | | - integrations: [], |
257 | | - }); |
258 | | - |
259 | | - const client = new NodeClient(options); |
260 | | - client.addIntegration(localVariables); |
261 | | - |
262 | | - await session.runPause(exceptionEvent100Frames); |
263 | | - |
264 | | - expect((localVariables as unknown as LocalVariablesPrivate)._getCachedFramesCount()).toBe(1); |
265 | | - |
266 | | - const frames = (localVariables as unknown as LocalVariablesPrivate)._getFirstCachedFrame(); |
267 | | - |
268 | | - expect(frames).toBeDefined(); |
269 | | - |
270 | | - const vars = frames as FrameVariables[]; |
271 | | - |
272 | | - expect(vars.length).toEqual(5); |
273 | | - }); |
274 | | - |
275 | | - it('Should not lookup variables for non-exception reasons', async () => { |
276 | | - const session = new MockDebugSession({}, { getLocalVariables: true }); |
277 | | - const localVariables = new LocalVariablesSync({}, session); |
278 | | - const options = getDefaultNodeClientOptions({ |
279 | | - stackParser: defaultStackParser, |
280 | | - includeLocalVariables: true, |
281 | | - integrations: [], |
282 | | - }); |
283 | | - |
284 | | - const client = new NodeClient(options); |
285 | | - client.addIntegration(localVariables); |
286 | | - |
287 | | - const nonExceptionEvent = { |
288 | | - method: exceptionEvent.method, |
289 | | - params: { ...exceptionEvent.params, reason: 'non-exception-reason' }, |
290 | | - }; |
291 | | - |
292 | | - await session.runPause(nonExceptionEvent); |
293 | | - |
294 | | - expect((localVariables as unknown as LocalVariablesPrivate)._getCachedFramesCount()).toBe(0); |
295 | | - }); |
296 | | - |
297 | | - it('Should not initialize when disabled', async () => { |
298 | | - const session = new MockDebugSession({}, { configureAndConnect: true }); |
299 | | - const localVariables = new LocalVariablesSync({}, session); |
300 | | - const options = getDefaultNodeClientOptions({ |
301 | | - stackParser: defaultStackParser, |
302 | | - integrations: [], |
303 | | - }); |
304 | | - |
305 | | - const client = new NodeClient(options); |
306 | | - client.addIntegration(localVariables); |
307 | | - |
308 | | - const eventProcessors = client['_eventProcessors']; |
309 | | - const eventProcessor = eventProcessors.find(processor => processor.id === 'LocalVariables'); |
310 | | - |
311 | | - expect(eventProcessor).toBeDefined(); |
312 | | - }); |
313 | | - |
314 | | - it('Should not initialize when inspector not loaded', async () => { |
315 | | - const localVariables = new LocalVariablesSync({}, undefined); |
316 | | - const options = getDefaultNodeClientOptions({ |
317 | | - stackParser: defaultStackParser, |
318 | | - integrations: [], |
319 | | - }); |
320 | | - |
321 | | - const client = new NodeClient(options); |
322 | | - client.addIntegration(localVariables); |
323 | | - |
324 | | - const eventProcessors = client['_eventProcessors']; |
325 | | - const eventProcessor = eventProcessors.find(processor => processor.id === 'LocalVariables'); |
326 | | - |
327 | | - expect(eventProcessor).toBeDefined(); |
328 | | - }); |
329 | | - |
330 | | - it('Should cache identical uncaught exception events', async () => { |
331 | | - const session = new MockDebugSession({ |
332 | | - '-6224981551105448869.1.2': { name: 'tim' }, |
333 | | - '-6224981551105448869.1.6': { arr: [1, 2, 3] }, |
334 | | - }); |
335 | | - const localVariables = new LocalVariablesSync({}, session); |
336 | | - const options = getDefaultNodeClientOptions({ |
337 | | - stackParser: defaultStackParser, |
338 | | - includeLocalVariables: true, |
339 | | - integrations: [], |
340 | | - }); |
341 | | - |
342 | | - const client = new NodeClient(options); |
343 | | - client.addIntegration(localVariables); |
344 | | - |
345 | | - await session.runPause(exceptionEvent); |
346 | | - await session.runPause(exceptionEvent); |
347 | | - await session.runPause(exceptionEvent); |
348 | | - await session.runPause(exceptionEvent); |
349 | | - await session.runPause(exceptionEvent); |
350 | | - |
351 | | - expect((localVariables as unknown as LocalVariablesPrivate)._getCachedFramesCount()).toBe(1); |
352 | | - }); |
353 | | - |
354 | 10 | describe('createCallbackList', () => { |
355 | 11 | it('Should call callbacks in reverse order', done => { |
356 | 12 | const log: number[] = []; |
|
0 commit comments