mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:39:49 +00:00
[dds] Add support for DAP sourceRequest to download source from the VM
Change-Id: Icb639c7008d51d6ffbecb95e362cc07c81993424 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209103 Reviewed-by: Ben Konyi <bkonyi@google.com> Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
parent
9e928c8587
commit
3d3a48ff9a
|
@ -21,6 +21,14 @@ import '../protocol_converter.dart';
|
|||
import '../protocol_generated.dart';
|
||||
import '../protocol_stream.dart';
|
||||
|
||||
/// The mime type to send with source responses to the client.
|
||||
///
|
||||
/// This is used so if the source name does not end with ".dart" the client can
|
||||
/// still tell which language to use (for syntax highlighting, etc.).
|
||||
///
|
||||
/// https://github.com/microsoft/vscode/issues/8182#issuecomment-231151640
|
||||
const dartMimeType = 'text/x-dart';
|
||||
|
||||
/// Maximum number of toString()s to be called when responding to variables
|
||||
/// requests from the client.
|
||||
///
|
||||
|
@ -723,6 +731,44 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
|
|||
await _dds?.shutdown();
|
||||
}
|
||||
|
||||
/// [sourceRequest] is called by the client to request source code for a given
|
||||
/// source.
|
||||
///
|
||||
/// The client may provide a whole source or just an int sourceReference (the
|
||||
/// spec originally had only sourceReference but now supports whole sources).
|
||||
///
|
||||
/// The supplied sourceReference should correspond to a ScriptRef instance
|
||||
/// that was stored to generate the sourceReference when sent to the client.
|
||||
@override
|
||||
Future<void> sourceRequest(
|
||||
Request request,
|
||||
SourceArguments args,
|
||||
void Function(SourceResponseBody) sendResponse,
|
||||
) async {
|
||||
final storedData = _isolateManager.getStoredData(
|
||||
args.source?.sourceReference ?? args.sourceReference,
|
||||
);
|
||||
if (storedData == null) {
|
||||
throw StateError('source reference is no longer valid');
|
||||
}
|
||||
final thread = storedData.thread;
|
||||
final data = storedData.data;
|
||||
final scriptRef = data is vm.ScriptRef ? data : null;
|
||||
if (scriptRef == null) {
|
||||
throw StateError('source reference was not a valid script');
|
||||
}
|
||||
|
||||
final script = await thread.getScript(scriptRef);
|
||||
final scriptSource = script.source;
|
||||
if (scriptSource == null) {
|
||||
throw DebugAdapterException('<source not available>');
|
||||
}
|
||||
|
||||
sendResponse(
|
||||
SourceResponseBody(content: scriptSource, mimeType: dartMimeType),
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles a request from the client for the call stack for [args.threadId].
|
||||
///
|
||||
/// This is usually called after we sent a [StoppedEvent] to the client
|
||||
|
|
|
@ -204,6 +204,12 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments> {
|
|||
void Function(SetExceptionBreakpointsResponseBody) sendResponse,
|
||||
);
|
||||
|
||||
Future<void> sourceRequest(
|
||||
Request request,
|
||||
SourceArguments args,
|
||||
void Function(SourceResponseBody) sendResponse,
|
||||
);
|
||||
|
||||
Future<void> stackTraceRequest(
|
||||
Request request,
|
||||
StackTraceArguments args,
|
||||
|
@ -312,6 +318,8 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments> {
|
|||
handle(request, threadsRequest, _voidArgs);
|
||||
} else if (request.command == 'stackTrace') {
|
||||
handle(request, stackTraceRequest, StackTraceArguments.fromJson);
|
||||
} else if (request.command == 'source') {
|
||||
handle(request, sourceRequest, SourceArguments.fromJson);
|
||||
} else if (request.command == 'scopes') {
|
||||
handle(request, scopesRequest, ScopesArguments.fromJson);
|
||||
} else if (request.command == 'variables') {
|
||||
|
|
|
@ -129,6 +129,45 @@ void main(List<String> args) async {
|
|||
final vmServiceUri = _extractVmServiceUri(outputEvents.first);
|
||||
expect(vmServiceUri.path, matches(vmServiceAuthCodePathPattern));
|
||||
});
|
||||
|
||||
test('can download source code from the VM', () async {
|
||||
final client = dap.client;
|
||||
final testFile = dap.createTestFile(simpleBreakpointProgram);
|
||||
final breakpointLine = lineWith(testFile, '// BREAKPOINT');
|
||||
|
||||
// Hit the initial breakpoint.
|
||||
final stop = await dap.client.hitBreakpoint(
|
||||
testFile,
|
||||
breakpointLine,
|
||||
launch: () => client.launch(
|
||||
testFile.path,
|
||||
debugSdkLibraries: true,
|
||||
),
|
||||
);
|
||||
|
||||
// Step in to go into print.
|
||||
final responses = await Future.wait([
|
||||
client.expectStop('step', sourceName: 'dart:core/print.dart'),
|
||||
client.stepIn(stop.threadId!),
|
||||
], eagerError: true);
|
||||
final stopResponse = responses.first as StoppedEventBody;
|
||||
|
||||
// Fetch the top stack frame (which should be inside print).
|
||||
final stack = await client.getValidStack(
|
||||
stopResponse.threadId!,
|
||||
startFrame: 0,
|
||||
numFrames: 1,
|
||||
);
|
||||
final topFrame = stack.stackFrames.first;
|
||||
|
||||
// SDK sources should have a sourceReference and no path.
|
||||
expect(topFrame.source!.path, isNull);
|
||||
expect(topFrame.source!.sourceReference, greaterThan(0));
|
||||
|
||||
// Source code should contain the implementation/signature of print().
|
||||
final source = await client.getValidSource(topFrame.source!);
|
||||
expect(source.content, contains('void print(Object? object) {'));
|
||||
});
|
||||
// These tests can be slow due to starting up the external server process.
|
||||
}, timeout: Timeout.none);
|
||||
|
||||
|
|
|
@ -217,6 +217,18 @@ class DapTestClient {
|
|||
_channel.sendResponse(response);
|
||||
}
|
||||
|
||||
/// Sends a source request to the server to request source code for a [source]
|
||||
/// reference that may have come from a stack frame or similar.
|
||||
///
|
||||
/// Returns a Future that completes when the server returns a corresponding
|
||||
/// response.
|
||||
Future<Response> source(Source source) => sendRequest(
|
||||
SourceArguments(
|
||||
source: source,
|
||||
sourceReference: source.sourceReference!,
|
||||
),
|
||||
);
|
||||
|
||||
/// Sends a stackTrace request to the server to request the call stack for a
|
||||
/// given thread.
|
||||
///
|
||||
|
@ -475,6 +487,14 @@ extension DapTestClientExtension on DapTestClient {
|
|||
response.body as Map<String, Object?>);
|
||||
}
|
||||
|
||||
/// Fetches source for a sourceReference and asserts it was a valid response.
|
||||
Future<SourceResponseBody> getValidSource(Source source) async {
|
||||
final response = await this.source(source);
|
||||
expect(response.success, isTrue);
|
||||
expect(response.command, equals('source'));
|
||||
return SourceResponseBody.fromJson(response.body as Map<String, Object?>);
|
||||
}
|
||||
|
||||
/// Fetches threads and asserts a valid response.
|
||||
Future<ThreadsResponseBody> getValidThreads() async {
|
||||
final response = await threads();
|
||||
|
|
Loading…
Reference in a new issue