Skip to content

Commit 7ec6bdc

Browse files
DanTupCommit Queue
authored andcommitted
[dds/dap] Provide reason/message for unverified breakpoints
This adds some additional information to breakpoint response/events so that in VS Code a grey breakpoint has a tooltip that explains the reason. Fixes Dart-Code/Dart-Code#5314 Change-Id: I1defdbc9b62b8536b81efbe3f01626e6faf8db50 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/409280 Reviewed-by: Ben Konyi <[email protected]> Commit-Queue: Derek Xu <[email protected]> Reviewed-by: Derek Xu <[email protected]>
1 parent a31be9b commit 7ec6bdc

File tree

3 files changed

+116
-1
lines changed

3 files changed

+116
-1
lines changed

pkg/dds/lib/src/dap/adapters/dart.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1583,7 +1583,11 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
15831583
// Send breakpoints back as unverified and with our generated IDs so we
15841584
// can update them with a 'breakpoint' event when we get the
15851585
// 'BreakpointAdded'/'BreakpointResolved' events from the VM.
1586-
.map((bp) => Breakpoint(id: bp.id, verified: false))
1586+
.map((bp) => Breakpoint(
1587+
id: bp.id,
1588+
verified: false,
1589+
message: 'Breakpoint has not yet been resolved',
1590+
reason: 'pending'))
15871591
.toList(),
15881592
));
15891593
completer.complete();

pkg/dds/lib/src/dap/isolate_manager.dart

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ class IsolateManager {
131131
/// Any leading character matched in place of the dollar is in the first capture.
132132
final _braceNotPrefixedByDollarOrBackslashPattern = RegExp(r'(^|[^\\\$]){');
133133

134+
/// A [RegExp] to extract the useful part of an error message when adding
135+
/// breakpoints so that the tooltip shown in editors can be less wordy.
136+
final _terseBreakpointFailureRegex =
137+
RegExp(r'Error occurred when resolving breakpoint location: (.*?)\.?$');
138+
134139
IsolateManager(this._adapter);
135140

136141
/// A list of all current active isolates.
@@ -853,6 +858,48 @@ class IsolateManager {
853858
);
854859
}
855860

861+
/// Queues a breakpoint event that passes an error reason from the VM back to
862+
/// the client.
863+
///
864+
/// This queue will be processed only after the client has been given the ID
865+
/// of this breakpoint. If that has already happened, the event will be
866+
/// processed on the next task queue iteration.
867+
void queueFailedBreakpointEvent(
868+
Object error,
869+
ClientBreakpoint clientBreakpoint,
870+
) {
871+
// Attempt to clean up the message to something that fits better in a
872+
// tooltip.
873+
//
874+
// An example failure is:
875+
//
876+
// addBreakpointWithScriptUri: Cannot add breakpoint at line 8.
877+
// Error occurred when resolving breakpoint location:
878+
// No debuggable code where breakpoint was requested.
879+
var userMessage = error is vm.RPCError
880+
? error.details ?? error.toString()
881+
: error.toString();
882+
var terseMessageMatch =
883+
_terseBreakpointFailureRegex.firstMatch(userMessage);
884+
if (terseMessageMatch != null) {
885+
userMessage = terseMessageMatch.group(1) ?? userMessage;
886+
}
887+
888+
final updatedBreakpoint = Breakpoint(
889+
id: clientBreakpoint.id,
890+
verified: false,
891+
message: userMessage,
892+
reason: 'failed',
893+
);
894+
// Ensure we don't send the breakpoint event until the client has been
895+
// given the breakpoint ID by queueing it.
896+
clientBreakpoint.queueAction(
897+
() => _adapter.sendEvent(
898+
BreakpointEventBody(breakpoint: updatedBreakpoint, reason: 'changed'),
899+
),
900+
);
901+
}
902+
856903
/// Attempts to resolve [uris] to file:/// URIs via the VM Service.
857904
///
858905
/// This method calls the VM service directly. Most requests to resolve URIs
@@ -1017,6 +1064,7 @@ class IsolateManager {
10171064
// request as it's very easy for editors to send us breakpoints that
10181065
// aren't valid any more.
10191066
_adapter.logger?.call('Failed to add breakpoint $e');
1067+
queueFailedBreakpointEvent(e, bp);
10201068
}
10211069
});
10221070
}

pkg/dds/test/dap/integration/debug_breakpoints_test.dart

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,69 @@ main() {
7575
expect(resolvedBreakpoints, addedBreakpoints);
7676
});
7777

78+
testWithUriConfigurations(
79+
() => dap, 'provides reason for failed breakpoints', () async {
80+
final client = dap.client;
81+
final testFile = dap.createTestFile(debuggerPauseProgram);
82+
final invalidBreakpointLine = 9999;
83+
84+
// Start running the app.
85+
await Future.wait([
86+
client.initialize(),
87+
client.launch(testFile.path),
88+
], eagerError: true);
89+
90+
// Collect reasons from all breakpoint events.
91+
final breakpointReasons = <String?>[];
92+
final breakpointChangedSubscription =
93+
client.breakpointChangeEvents.listen((event) {
94+
breakpointReasons
95+
.add('${event.breakpoint.reason}: ${event.breakpoint.message}');
96+
});
97+
98+
// Set the breakpoint and also collect the original reason.
99+
var bps = await client.setBreakpoint(testFile, invalidBreakpointLine);
100+
var bp = bps.breakpoints.single;
101+
breakpointReasons.add('${bp.reason}: ${bp.message}');
102+
103+
// Wait up to a few seconds for the change events to come through to
104+
// allow for slow CI bots, but exit early if they all arrived.
105+
final testUntil = DateTime.now().toUtc().add(const Duration(seconds: 5));
106+
while (DateTime.now().toUtc().isBefore(testUntil) &&
107+
breakpointReasons.length < 2) {
108+
await pumpEventQueue(times: 5000);
109+
}
110+
111+
expect(
112+
breakpointReasons,
113+
equals([
114+
'pending: Breakpoint has not yet been resolved',
115+
'failed: No debuggable code where breakpoint was requested'
116+
]));
117+
118+
await breakpointChangedSubscription.cancel();
119+
});
120+
121+
testWithUriConfigurations(
122+
() => dap, 'provides reason for not-yet-resolved breakpoints',
123+
() async {
124+
final client = dap.client;
125+
final testFile = dap.createTestFile(debuggerPauseProgram);
126+
final breakpointLine = lineWith(testFile, breakpointMarker);
127+
128+
// Start running the app.
129+
await Future.wait([
130+
client.initialize(),
131+
client.launch(testFile.path),
132+
], eagerError: true);
133+
134+
// Set a breakpoint and verify the result.
135+
var bps = await client.setBreakpoint(testFile, breakpointLine);
136+
expect(bps.breakpoints.single.reason, 'pending');
137+
expect(bps.breakpoints.single.message,
138+
'Breakpoint has not yet been resolved');
139+
});
140+
78141
test('responds to setBreakpoints before any breakpoint events', () async {
79142
final client = dap.client;
80143
final testFile =

0 commit comments

Comments
 (0)