Skip to content

Commit 8f4010a

Browse files
authored
updating all views on receiving invalidated or memory events (#1004)
* updating all views on receiving invalidated event or memory events
1 parent 415fc3c commit 8f4010a

7 files changed

Lines changed: 238 additions & 10 deletions

File tree

src/debug-session/__test__/debug-session.factory.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,21 @@
1515
*/
1616
// generated with AI
1717

18+
import type { DebugProtocol } from '@vscode/debugprotocol';
1819
import { GDBTargetDebugSession, GDBTargetDebugTracker, TargetState } from '..';
1920

2021
export type OnRefreshCallback = (session: Session) => void;
2122

23+
export type TestMemoryEvent = {
24+
session: Session;
25+
event: DebugProtocol.MemoryEvent;
26+
};
27+
28+
export type TestInvalidatedEvent = {
29+
session: Session;
30+
event: DebugProtocol.InvalidatedEvent;
31+
};
32+
2233
export type Session = {
2334
session: { id: string };
2435
getCbuildRun: () => Promise<{ getScvdFilePaths: () => string[] } | undefined>;
@@ -35,13 +46,17 @@ export type TrackerCallbacks = {
3546
onStackTrace: (cb: (session: { session: Session }) => Promise<void>) => { dispose: jest.Mock };
3647
onDidChangeActiveStackItem: (cb: (session: { session: Session }) => Promise<void>) => { dispose: jest.Mock };
3748
onWillStartSession: (cb: (session: Session) => Promise<void>) => { dispose: jest.Mock };
49+
onMemory: (cb: (event: TestMemoryEvent) => Promise<void>) => { dispose: jest.Mock };
50+
onInvalidated: (cb: (event: TestInvalidatedEvent) => Promise<void>) => { dispose: jest.Mock };
3851
callbacks: Partial<{
3952
willStop: (session: Session) => Promise<void>;
4053
connected: (session: Session) => Promise<void>;
4154
activeSession: (session: Session | undefined) => Promise<void>;
4255
stackTrace: (session: { session: Session }) => Promise<void>;
4356
activeStackItem: (session: { session: Session }) => Promise<void>;
4457
willStart: (session: Session) => Promise<void>;
58+
memory: (event: TestMemoryEvent) => Promise<void>;
59+
invalidated: (event: TestInvalidatedEvent) => Promise<void>;
4560
}>;
4661
};
4762

@@ -73,6 +88,14 @@ export const trackerFactory = (): TrackerCallbacks => {
7388
callbacks.willStart = cb;
7489
return { dispose: jest.fn() };
7590
},
91+
onMemory: (cb) => {
92+
callbacks.memory = cb;
93+
return { dispose: jest.fn() };
94+
},
95+
onInvalidated: (cb) => {
96+
callbacks.invalidated = cb;
97+
return { dispose: jest.fn() };
98+
},
7699
};
77100
};
78101

src/debug-session/gdbtarget-debug-tracker.test.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import {
2020
GDBTargetDebugTracker,
2121
SessionStackItem,
2222
SessionStackTrace,
23-
StoppedEvent
23+
StoppedEvent,
24+
MemoryEvent,
25+
InvalidatedEvent
2426
} from './gdbtarget-debug-tracker';
2527
import { debugSessionFactory, extensionContextFactory } from '../__test__/vscode.factory';
2628
import { GDBTargetDebugSession } from './gdbtarget-debug-session';
@@ -223,6 +225,62 @@ describe('GDBTargetDebugTracker', () => {
223225
expect(result!.event).toEqual(continuedEvent);
224226
});
225227

228+
it('sends a session memory event', async () => {
229+
let gdbSession: GDBTargetDebugSession|undefined = undefined;
230+
debugTracker.onWillStartSession(session => gdbSession = session);
231+
let result: MemoryEvent|undefined = undefined;
232+
debugTracker.onMemory(event => result = event);
233+
const tracker = await adapterFactory!.createDebugAdapterTracker(debugSessionFactory(debugConfigurationFactory()));
234+
tracker!.onWillStartSession!();
235+
const memoryEvent: DebugProtocol.MemoryEvent = {
236+
event: 'memory',
237+
type: 'event',
238+
seq: 1,
239+
body: {
240+
memoryReference: '0x1234',
241+
offset: 0,
242+
count: 4
243+
}
244+
};
245+
tracker!.onDidSendMessage!(memoryEvent);
246+
expect(gdbSession).toBeDefined();
247+
expect(result).toBeDefined();
248+
expect(result!.session).toEqual(gdbSession);
249+
expect(result!.event).toEqual(memoryEvent);
250+
});
251+
252+
it('sends an Invalidated event on receiving an invalidated event', async () => {
253+
let gdbSession: GDBTargetDebugSession|undefined = undefined;
254+
debugTracker.onWillStartSession(session => gdbSession = session);
255+
let result: InvalidatedEvent|undefined = undefined;
256+
debugTracker.onInvalidated(event => result = event);
257+
const tracker = await adapterFactory!.createDebugAdapterTracker(debugSessionFactory(debugConfigurationFactory()));
258+
tracker!.onWillStartSession!();
259+
const stoppedEvent: DebugProtocol.StoppedEvent = {
260+
event: 'stopped',
261+
type: 'event',
262+
seq: 1,
263+
body: {
264+
reason: 'step',
265+
threadId: 1
266+
}
267+
};
268+
tracker!.onDidSendMessage!(stoppedEvent);
269+
const invalidatedEvent: DebugProtocol.InvalidatedEvent = {
270+
event: 'invalidated',
271+
type: 'event',
272+
seq: 2,
273+
body: {
274+
areas: ['stack'],
275+
threadId: 1
276+
}
277+
};
278+
tracker!.onDidSendMessage!(invalidatedEvent);
279+
expect(gdbSession).toBeDefined();
280+
expect(result).toBeDefined();
281+
expect(result!.session).toEqual(gdbSession);
282+
});
283+
226284
it('sends a session stopped event', async () => {
227285
let gdbSession: GDBTargetDebugSession|undefined = undefined;
228286
debugTracker.onWillStartSession(session => gdbSession = session);

src/debug-session/gdbtarget-debug-tracker.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export interface SessionEvent<T extends DebugProtocol.Event> {
2828

2929
export type ContinuedEvent = SessionEvent<DebugProtocol.ContinuedEvent>;
3030
export type StoppedEvent = SessionEvent<DebugProtocol.StoppedEvent>;
31+
export type MemoryEvent = SessionEvent<DebugProtocol.MemoryEvent>;
32+
export type InvalidatedEvent = SessionEvent<DebugProtocol.InvalidatedEvent>;
3133

3234
export interface SessionCapabilities {
3335
session: GDBTargetDebugSession;
@@ -78,6 +80,12 @@ export class GDBTargetDebugTracker {
7880
private readonly _onStackTrace: vscode.EventEmitter<SessionStackTrace> = new vscode.EventEmitter<SessionStackTrace>();
7981
public readonly onStackTrace: vscode.Event<SessionStackTrace> = this._onStackTrace.event;
8082

83+
private readonly _onMemory: vscode.EventEmitter<MemoryEvent> = new vscode.EventEmitter<MemoryEvent>();
84+
public readonly onMemory: vscode.Event<MemoryEvent> = this._onMemory.event;
85+
86+
private readonly _onInvalidated: vscode.EventEmitter<InvalidatedEvent> = new vscode.EventEmitter<InvalidatedEvent>();
87+
public readonly onInvalidated: vscode.Event<InvalidatedEvent> = this._onInvalidated.event;
88+
8189
public activate(context: vscode.ExtensionContext) {
8290
const createDebugAdapterTracker = (session: vscode.DebugSession): vscode.ProviderResult<vscode.DebugAdapterTracker> => {
8391
return {
@@ -151,6 +159,16 @@ export class GDBTargetDebugTracker {
151159
case 'output':
152160
gdbTargetSession?.filterOutputEvent(event as DebugProtocol.OutputEvent);
153161
break;
162+
case 'invalidated':
163+
if (gdbTargetSession) {
164+
this._onInvalidated.fire({ session: gdbTargetSession, event: event as DebugProtocol.InvalidatedEvent });
165+
}
166+
break;
167+
case 'memory':
168+
if (gdbTargetSession) {
169+
this._onMemory.fire({ session: gdbTargetSession, event: event as DebugProtocol.MemoryEvent });
170+
}
171+
break;
154172
}
155173
}
156174

src/views/component-viewer/component-viewer-base.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export interface ScvdCollector {
3030
getScvdFilePaths(session: GDBTargetDebugSession): Promise<string[]>;
3131
}
3232

33-
export type UpdateReason = 'sessionChanged' | 'refreshTimer' | 'stackTrace' | 'stackItemChanged' | 'unlockingInstance';
33+
export type UpdateReason = 'sessionChanged' | 'refreshTimer' | 'stackTrace' | 'stackItemChanged' | 'unlockingInstance' | 'invalidated' | 'memoryEvent';
3434

3535
export interface ComponentViewerInstancesWrapper {
3636
componentViewerInstance: ComponentViewerInstance;
@@ -328,14 +328,24 @@ export class ComponentViewerBase {
328328
const onWillStartSessionDisposable = tracker.onWillStartSession(async (session) => {
329329
await this.handleOnWillStartSession(session);
330330
});
331+
const onMemoryDisposable = tracker.onMemory(async (event) => {
332+
const session = event.session;
333+
await this.handleOnMemoryEvent(session);
334+
});
335+
const onInvalidatedDisposable = tracker.onInvalidated(async (event) => {
336+
const session = event.session;
337+
await this.handleOnInvalidated(session);
338+
});
331339
// clear all disposables on extension deactivation
332340
this._context.subscriptions.push(
333341
onWillStopSessionDisposable,
334342
onConnectedDisposable,
335343
onDidChangeActiveDebugSessionDisposable,
336344
onStackTraceDisposable,
337345
onDidChangeActiveStackItemDisposable,
338-
onWillStartSessionDisposable
346+
onWillStartSessionDisposable,
347+
onMemoryDisposable,
348+
onInvalidatedDisposable
339349
);
340350
}
341351

@@ -348,6 +358,20 @@ export class ComponentViewerBase {
348358
this.schedulePendingUpdate('stackTrace');
349359
}
350360

361+
protected async handleOnMemoryEvent(session: GDBTargetDebugSession): Promise<void> {
362+
if (this._activeSession?.session.id !== session.session.id) {
363+
return;
364+
}
365+
this.schedulePendingUpdate('memoryEvent');
366+
}
367+
368+
protected async handleOnInvalidated(session: GDBTargetDebugSession): Promise<void> {
369+
if (this._activeSession?.session.id !== session.session.id) {
370+
return;
371+
}
372+
this.schedulePendingUpdate('invalidated');
373+
}
374+
351375
protected async handleOnStackItemChanged(session: GDBTargetDebugSession): Promise<void> {
352376
// If the active session is not the one being updated, update it.
353377
// This can happen when a session is started and stack trace/item events are emitted before the session is set as active in the component viewer.

src/views/component-viewer/test/unit/component-viewer-base.test.ts

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*/
2121

2222
import * as vscode from 'vscode';
23+
import type { DebugProtocol } from '@vscode/debugprotocol';
2324
import type { GDBTargetDebugTracker } from '../../../../debug-session';
2425
import type { GDBTargetDebugSession } from '../../../../debug-session/gdbtarget-debug-session';
2526
import { componentViewerLogger } from '../../../../logger';
@@ -38,6 +39,7 @@ const instanceFactory = jest.fn(() => ({
3839
getGuiTree: jest.fn<ScvdGuiInterface[] | undefined, []>(() => []),
3940
updateActiveSession: jest.fn(),
4041
cancelExecution: jest.fn(),
42+
setSvdPath: jest.fn(),
4143
}));
4244

4345
jest.mock('../../component-viewer-instance', () => ({
@@ -102,6 +104,26 @@ const getReadScvdFiles = (controller: TestClass) =>
102104
// Local test mocks
103105
type ExpansionEventCallback = (event: vscode.TreeViewExpansionEvent<ScvdGuiInterface>) => void;
104106

107+
const makeMemoryEvent = (): DebugProtocol.MemoryEvent => ({
108+
event: 'memory',
109+
type: 'event',
110+
seq: 1,
111+
body: {
112+
memoryReference: '0x1234',
113+
offset: 0,
114+
count: 4,
115+
},
116+
});
117+
118+
const makeInvalidatedEvent = (): DebugProtocol.InvalidatedEvent => ({
119+
event: 'invalidated',
120+
type: 'event',
121+
seq: 1,
122+
body: {
123+
areas: ['variables'],
124+
},
125+
});
126+
105127
const createController = (
106128
context: vscode.ExtensionContext = extensionContextFactory(),
107129
provider: ComponentViewerTreeDataProvider = treeDataProviderFactory()
@@ -157,12 +179,12 @@ describe('ComponentViewerBase', () => {
157179
expect(vscode.commands.registerCommand).toHaveBeenCalledWith('vscode-cmsis-debugger.testClass.lockComponent', expect.any(Function));
158180
expect(vscode.commands.registerCommand).toHaveBeenCalledWith('vscode-cmsis-debugger.testClass.unlockComponent', expect.any(Function));
159181
expect(vscode.commands.registerCommand).toHaveBeenCalledWith('vscode-cmsis-debugger.testClass.expandAll', expect.any(Function));
160-
// 1 tree view + 2 event listeners + 7 commands + 6 tracker disposables
161-
expect(context.subscriptions.length).toBe(16);
182+
// 1 tree view + 2 event listeners + 7 commands + 8 tracker disposables
183+
expect(context.subscriptions.length).toBe(18);
162184
expect(vscode.commands.registerCommand).toHaveBeenCalledWith('vscode-cmsis-debugger.testClass.filterTree', expect.any(Function));
163185
expect(vscode.commands.registerCommand).toHaveBeenCalledWith('vscode-cmsis-debugger.testClass.clearFilter', expect.any(Function));
164-
// 1 tree view + 2 event listeners + 7 commands + 6 tracker disposables
165-
expect(context.subscriptions.length).toBe(16);
186+
// 1 tree view + 2 event listeners + 7 commands + 8 tracker disposables
187+
expect(context.subscriptions.length).toBe(18);
166188
});
167189

168190
it('should fail to activate the test class tree data provider if view is not correctly loaded', async () => {
@@ -213,6 +235,22 @@ describe('ComponentViewerBase', () => {
213235
expect(instanceFactory).toHaveBeenCalledTimes(2);
214236
});
215237

238+
it('sets svd path from debug configuration when reading scvd files', async () => {
239+
const session = debugSessionFactory('s1', ['a.scvd']);
240+
(session.session as unknown as { configuration: { definitionPath: string } }).configuration = {
241+
definitionPath: '/device.svd',
242+
};
243+
(controller as unknown as { _activeSession?: Session })._activeSession = session;
244+
245+
const instance = instanceFactory();
246+
instanceFactory.mockImplementationOnce(() => instance);
247+
248+
const readScvdFiles = getReadScvdFiles(controller);
249+
await readScvdFiles(tracker, session);
250+
251+
expect(instance.setSvdPath).toHaveBeenCalledWith('/device.svd');
252+
});
253+
216254
it('skips reading scvd files when no active session is set', async () => {
217255
const session = debugSessionFactory('s1', ['a.scvd']);
218256
const readScvdFiles = getReadScvdFiles(controller);
@@ -235,6 +273,7 @@ describe('ComponentViewerBase', () => {
235273
getGuiTree: jest.fn(() => []),
236274
updateActiveSession: jest.fn(),
237275
cancelExecution: jest.fn(),
276+
setSvdPath: jest.fn(),
238277
}));
239278
const showErrorSpy = jest.spyOn(vscode.window, 'showErrorMessage').mockResolvedValue(undefined);
240279
const errorSpy = jest.spyOn(componentViewerLogger, 'error');
@@ -292,6 +331,9 @@ describe('ComponentViewerBase', () => {
292331
await tracker.callbacks.stackTrace?.({ session });
293332
expect((controller as unknown as { _activeSession?: Session })._activeSession).toBe(session);
294333

334+
await tracker.callbacks.memory?.({ session, event: makeMemoryEvent() });
335+
await tracker.callbacks.invalidated?.({ session, event: makeInvalidatedEvent() });
336+
295337

296338
(controller as unknown as { _activeSession?: Session })._activeSession = session;
297339
await tracker.callbacks.willStop?.(session);
@@ -351,6 +393,36 @@ describe('ComponentViewerBase', () => {
351393
expect((controller as unknown as { _activeSession?: Session })._activeSession).toBe(sessionA);
352394
});
353395

396+
it('updates instances on memory event', async () => {
397+
const sessionA = debugSessionFactory('s1', [], 'stopped');
398+
399+
(controller as unknown as { _activeSession?: Session })._activeSession = sessionA;
400+
401+
const scheduleSpy = jest
402+
.spyOn(controller as unknown as { schedulePendingUpdate: (reason: UpdateReason) => void }, 'schedulePendingUpdate')
403+
.mockImplementation(() => undefined);
404+
405+
const handleOnMemoryEvent = (controller as unknown as { handleOnMemoryEvent: (s: Session) => Promise<void> }).handleOnMemoryEvent.bind(controller);
406+
await handleOnMemoryEvent(sessionA);
407+
408+
expect(scheduleSpy).toHaveBeenCalledWith('memoryEvent');
409+
});
410+
411+
it('updates instances on invalidated event', async () => {
412+
const sessionA = debugSessionFactory('s1', [], 'stopped');
413+
414+
(controller as unknown as { _activeSession?: Session })._activeSession = sessionA;
415+
416+
const scheduleSpy = jest
417+
.spyOn(controller as unknown as { schedulePendingUpdate: (reason: UpdateReason) => void }, 'schedulePendingUpdate')
418+
.mockImplementation(() => undefined);
419+
420+
const handleOnInvalidated = (controller as unknown as { handleOnInvalidated: (s: Session) => Promise<void> }).handleOnInvalidated.bind(controller);
421+
await handleOnInvalidated(sessionA);
422+
423+
expect(scheduleSpy).toHaveBeenCalledWith('invalidated');
424+
});
425+
354426
it('does not update active session when stack item matches the active session', async () => {
355427
const sessionA = debugSessionFactory('s1');
356428
const updateSpy = jest.fn();

src/views/live-watch/live-watch.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ describe('LiveWatchTreeDataProvider', () => {
9797
expect((liveWatchTreeDataProvider as any).activeSession).toBeUndefined();
9898
});
9999

100-
it('refreshes on stopped event and on onDidChangeActiveStackItem', async () => {
100+
it('refreshes on stopped event and on onDidChangeActiveStackItem and onMemory event', async () => {
101101
const refreshSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'refresh').mockResolvedValue('');
102102
await liveWatchTreeDataProvider.activate(tracker);
103103
// Activate session
@@ -110,6 +110,14 @@ describe('LiveWatchTreeDataProvider', () => {
110110
// Fire onDidChangeActiveStackItem event
111111
(tracker as any)._onDidChangeActiveStackItem.fire({ item: { frameId: 1 } });
112112
expect(refreshSpy).toHaveBeenCalled();
113+
refreshSpy.mockClear();
114+
// Fire onMemory event
115+
(tracker as any)._onMemory.fire({ session: gdbtargetDebugSession, event: { memoryReference: '0x1234', offset: 0, count: 4 } });
116+
expect(refreshSpy).toHaveBeenCalled();
117+
refreshSpy.mockClear();
118+
// Fire onInvalidated event
119+
(tracker as any)._onInvalidated.fire({ session: gdbtargetDebugSession, event: { memoryReference: '0x1234', offset: 0, count: 4 } });
120+
expect(refreshSpy).toHaveBeenCalled();
113121
});
114122

115123
it('calls save function when extension is deactivating', async () => {

0 commit comments

Comments
 (0)