| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:dap/dap.dart'; |
| import 'package:dds/dds.dart'; |
| import 'package:dds/src/dap/constants.dart'; |
| import 'package:dds_service_extensions/dap.dart'; |
| import 'package:test/test.dart'; |
| import 'package:vm_service/vm_service.dart'; |
| import 'package:vm_service/vm_service_io.dart'; |
| |
| import 'common/test_helper.dart'; |
| |
| Future<Isolate> waitForFirstRunnableIsolate(VmService service) async { |
| VM vm; |
| do { |
| vm = await service.getVM(); |
| } while (vm.isolates!.isEmpty); |
| final isolateId = vm.isolates!.first.id!; |
| Isolate isolate; |
| do { |
| isolate = await service.getIsolate(isolateId); |
| } while (!isolate.runnable!); |
| return isolate; |
| } |
| |
| void main() { |
| Process? process; |
| DartDevelopmentService? dds; |
| late VmService service; |
| |
| Future<void> createProcess({bool pauseOnStart = true}) async { |
| process = await spawnDartProcess( |
| 'dap_over_dds_script.dart', |
| disableServiceAuthCodes: true, |
| pauseOnStart: pauseOnStart, |
| ); |
| } |
| |
| tearDown(() async { |
| await dds?.shutdown(); |
| process?.kill(); |
| process = null; |
| }); |
| |
| var nextSeq = 1; |
| Future<DapResponse> sendDapRequest(String request, |
| [Object? arguments]) async { |
| final result = await service.sendDapRequest( |
| jsonEncode( |
| Request( |
| command: request, |
| seq: nextSeq++, |
| arguments: arguments, |
| ), |
| ), |
| ); |
| |
| expect(result.dapResponse, isNotNull); |
| expect(result.dapResponse.type, 'response'); |
| expect(result.dapResponse.message, isNull); |
| expect(result.dapResponse.success, true); |
| expect(result.dapResponse.command, request); |
| |
| return result; |
| } |
| |
| Future<String?> instanceToString(String isolateId, String instanceId) async { |
| final result = await service.invoke(isolateId, instanceId, 'toString', []); |
| return result is InstanceRef ? result.valueAsString : null; |
| } |
| |
| Future<String> variableToString(int variablesReference) async { |
| // Because variables requests are for _child_ variables, a converted |
| // Instance->Variable will return an initial container that has a single |
| // 'value' field, which contains the string representation of the variable |
| // as its value, and a further 'variablesReference' that can be used to |
| // fetch child variables. |
| final variablesResult = await sendDapRequest( |
| 'variables', |
| VariablesArguments(variablesReference: variablesReference), |
| ); |
| final variablesBody = VariablesResponseBody.fromMap( |
| variablesResult.dapResponse.body as Map<String, Object?>, |
| ); |
| expect(variablesBody.variables, hasLength(1)); |
| final variable = variablesBody.variables.single; |
| expect(variable.name, 'value'); |
| expect(variable.variablesReference, isPositive); |
| return variable.value; |
| } |
| |
| test('DDS responds to DAP message', () async { |
| await createProcess(); |
| dds = await DartDevelopmentService.startDartDevelopmentService( |
| remoteVmServiceUri, |
| ); |
| expect(dds!.isRunning, true); |
| final serviceUri = dds!.wsUri!; |
| service = await vmServiceConnectUri(serviceUri.toString()); |
| |
| final breakpointArguments = SetBreakpointsArguments( |
| breakpoints: [ |
| SourceBreakpoint(line: 20), |
| SourceBreakpoint(line: 30), |
| ], |
| source: Source( |
| name: 'main.dart', |
| path: '/file/to/main.dart', |
| ), |
| ); |
| |
| final result = await sendDapRequest('setBreakpoints', breakpointArguments); |
| final response = SetBreakpointsResponseBody.fromMap( |
| result.dapResponse.body as Map<String, Object?>); |
| expect(response.breakpoints, hasLength(2)); |
| expect(response.breakpoints[0].verified, isFalse); |
| expect(response.breakpoints[1].verified, isFalse); |
| }); |
| |
| test('DAP can map between variableReferences and InstanceRefs', () async { |
| await createProcess(pauseOnStart: false); |
| dds = await DartDevelopmentService.startDartDevelopmentService( |
| remoteVmServiceUri, |
| ); |
| service = await vmServiceConnectUri(dds!.wsUri!.toString()); |
| |
| // Wait for the isolate to become runnable as `evaluateInFrame` requires |
| // the isolate to be runnable. |
| final isolate = await waitForFirstRunnableIsolate(service); |
| final isolateId = isolate.id!; |
| |
| // Get the variable for 'myInstance'. |
| final originalInstanceRef = (await service.evaluateInFrame( |
| isolateId, 0, 'myInstance')) as InstanceRef; |
| final originalInstanceId = originalInstanceRef.id!; |
| |
| // Ask DAP to make a variableReference for it. |
| final createVariableResult = await sendDapRequest( |
| Command.createVariableForInstance, |
| { |
| Parameters.isolateId: isolateId, |
| Parameters.instanceId: originalInstanceId, |
| }, |
| ); |
| final createVariablesBody = |
| createVariableResult.dapResponse.body as Map<String, Object?>; |
| final variablesReference = |
| createVariablesBody[Parameters.variablesReference] as int; |
| |
| // And now ask DAP to convert it back to an instance ID. |
| final getInstanceResult = await sendDapRequest( |
| Command.getVariablesInstanceId, |
| { |
| Parameters.variablesReference: variablesReference, |
| }, |
| ); |
| final getInstanceRefBody = |
| getInstanceResult.dapResponse.body as Map<String, Object?>; |
| final mappedInstanceId = |
| getInstanceRefBody[Parameters.instanceId] as String; |
| |
| // Now verify that the string value of these are all the same. |
| expect(await instanceToString(isolateId, originalInstanceId), 'MyClass'); |
| expect(await variableToString(variablesReference), 'MyClass'); |
| expect(await instanceToString(isolateId, mappedInstanceId), 'MyClass'); |
| }); |
| |
| test('DAP includes Isolate IDs in Threads', () async { |
| await createProcess(pauseOnStart: false); |
| dds = await DartDevelopmentService.startDartDevelopmentService( |
| remoteVmServiceUri, |
| ); |
| service = await vmServiceConnectUri(dds!.wsUri!.toString()); |
| |
| // Get the expected isolateId. |
| final isolate = await waitForFirstRunnableIsolate(service); |
| final isolateId = isolate.id!; |
| |
| // Ask DAP for all threads. |
| final threadsResult = await sendDapRequest(Command.threads); |
| final threadsBody = threadsResult.dapResponse.body as Map<String, Object?>; |
| |
| final threads = threadsBody['threads'] as List; |
| expect(threads, hasLength(1)); |
| final thread = threads[0] as Map<String, Object?>; |
| expect(thread, { |
| 'id': 1, |
| 'name': 'main', |
| 'isolateId': isolateId, |
| }); |
| }); |
| } |