// 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 waitForFirstPausedRunnableIsolate(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! || isolate.pauseEvent == null); return isolate; } void main() { Process? process; DartDevelopmentService? dds; late VmService service; Future 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 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 instanceToString(String isolateId, String instanceId) async { final result = await service.invoke(isolateId, instanceId, 'toString', []); return result is InstanceRef ? result.valueAsString : null; } Future 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, ); 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); 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 waitForFirstPausedRunnableIsolate(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; 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; 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 waitForFirstPausedRunnableIsolate(service); final isolateId = isolate.id!; // Ask DAP for all threads. final threadsResult = await sendDapRequest(Command.threads); final threadsBody = threadsResult.dapResponse.body as Map; final threads = threadsBody['threads'] as List; expect(threads, hasLength(1)); final thread = threads[0] as Map; expect(thread, { 'id': 1, 'name': 'main', 'isolateId': isolateId, }); }); }