From 3b0f8335ee8b6cdac392cd9cfff7efab9c6ffba8 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Wed, 2 Nov 2022 18:38:46 +0000 Subject: [PATCH] [flutter_tools/dap] Add a base Flutter adapter class to avoid duplication between adapters (#114533) --- .../src/debug_adapters/flutter_adapter.dart | 159 ++++------------- .../debug_adapters/flutter_base_adapter.dart | 160 ++++++++++++++++++ .../debug_adapters/flutter_test_adapter.dart | 124 ++------------ .../lib/src/debug_adapters/mixins.dart | 36 ---- .../lib/src/debug_adapters/server.dart | 4 +- .../test/general.shard/dap/mocks.dart | 24 ++- 6 files changed, 231 insertions(+), 276 deletions(-) create mode 100644 packages/flutter_tools/lib/src/debug_adapters/flutter_base_adapter.dart diff --git a/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart b/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart index b26f2a98f64..4025da083e3 100644 --- a/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart +++ b/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart @@ -6,69 +6,35 @@ import 'dart:async'; import 'dart:math' as math; import 'package:dds/dap.dart' hide PidTracker; -import 'package:meta/meta.dart'; import 'package:vm_service/vm_service.dart' as vm; -import '../base/file_system.dart'; import '../base/io.dart'; -import '../base/platform.dart'; import '../cache.dart'; import '../convert.dart'; import 'flutter_adapter_args.dart'; -import 'mixins.dart'; +import 'flutter_base_adapter.dart'; /// A DAP Debug Adapter for running and debugging Flutter applications. -class FlutterDebugAdapter extends DartDebugAdapter - with PidTracker, FlutterAdapter { +class FlutterDebugAdapter extends FlutterBaseDebugAdapter { FlutterDebugAdapter( super.channel, { - required this.fileSystem, - required this.platform, + required super.fileSystem, + required super.platform, super.ipv6, - bool enableDds = true, + super.enableFlutterDds = true, super.enableAuthCodes, super.logger, super.onError, - }) : _enableDds = enableDds, - flutterSdkRoot = Cache.flutterRoot!, - // Always disable in the DAP layer as it's handled in the spawned - // 'flutter' process. - super(enableDds: false) { - configureOrgDartlangSdkMappings(); - } - - @override - FileSystem fileSystem; - Platform platform; - Process? _process; - - @override - final String flutterSdkRoot; - - /// Whether DDS should be enabled in the Flutter process. - /// - /// We never enable DDS in the DAP process for Flutter, so this value is not - /// the same as what is passed to the base class, which is always provided 'false'. - final bool _enableDds; - - @override - final FlutterLaunchRequestArguments Function(Map obj) - parseLaunchArgs = FlutterLaunchRequestArguments.fromJson; - - @override - final FlutterAttachRequestArguments Function(Map obj) - parseAttachArgs = FlutterAttachRequestArguments.fromJson; + }); /// A completer that completes when the app.started event has been received. - @visibleForTesting - final Completer appStartedCompleter = Completer(); + final Completer _appStartedCompleter = Completer(); /// Whether or not the app.started event has been received. - bool get _receivedAppStarted => appStartedCompleter.isCompleted; + bool get _receivedAppStarted => _appStartedCompleter.isCompleted; /// The appId of the current running Flutter app. - @visibleForTesting - String? appId; + String? _appId; /// The ID to use for the next request sent to the Flutter run daemon. int _flutterRequestId = 1; @@ -84,13 +50,6 @@ class FlutterDebugAdapter extends DartDebugAdapter true; - /// Whether the VM Service closing should be used as a signal to terminate the debug session. - /// - /// Since we always have a process for Flutter (whether run or attach) we'll - /// always use its termination instead, so this is always false. - @override - bool get terminateOnVmServiceClose => false; - /// Whether or not the user requested debugging be enabled. /// /// For debugging to be enabled, the user must have chosen "Debug" (and not @@ -104,17 +63,8 @@ class FlutterDebugAdapter extends DartDebugAdapter super.enableDebugger && !profileMode && !releaseMode; /// Whether the launch configuration arguments specify `--profile`. /// @@ -152,13 +102,13 @@ class FlutterDebugAdapter extends DartDebugAdapter progress.end())); + unawaited(_appStartedCompleter.future.then((_) => progress.end())); final String? vmServiceUri = args.vmServiceUri; final List toolArgs = [ 'attach', '--machine', - if (!_enableDds) '--no-dds', + if (!enableFlutterDds) '--no-dds', if (vmServiceUri != null) ...['--debug-uri', vmServiceUri], ]; @@ -206,28 +156,6 @@ class FlutterDebugAdapter extends DartDebugAdapter debuggerConnected(vm.VM vmInfo) async { - // Usually we'd capture the pid from the VM here and record it for - // terminating, however for Flutter apps it may be running on a remote - // device so it's not valid to terminate a process with that pid locally. - // For attach, pids should never be collected as terminateRequest() should - // not terminate the debugee. - } - - /// Called by [disconnectRequest] to request that we forcefully shut down the app being run (or in the case of an attach, disconnect). - /// - /// Client IDEs/editors should send a terminateRequest before a - /// disconnectRequest to allow a graceful shutdown. This method must terminate - /// quickly and therefore may leave orphaned processes. - @override - Future disconnectImpl() async { - if (isAttach) { - await preventBreakingAndResume(); - } - terminatePids(ProcessSignal.sigkill); - } - @override Future handleExtensionEvent(vm.Event event) async { await super.handleExtensionEvent(event); @@ -274,12 +202,12 @@ class FlutterDebugAdapter extends DartDebugAdapter progress.end())); + unawaited(_appStartedCompleter.future.then((_) => progress.end())); final List toolArgs = [ 'run', '--machine', - if (!_enableDds) '--no-dds', + if (!enableFlutterDds) '--no-dds', if (enableDebugger) '--start-paused', // Structured errors are enabled by default, but since we don't connect // the VM Service for noDebug, we need to disable them so that error text @@ -332,27 +260,6 @@ class FlutterDebugAdapter extends DartDebugAdapter launchAsProcess({ - required String executable, - required List processArgs, - required Map? env, - }) async { - logger?.call('Spawning $executable with $processArgs in ${args.cwd}'); - final Process process = await Process.start( - executable, - processArgs, - workingDirectory: args.cwd, - environment: env, - ); - _process = process; - pidsToTerminate.add(process.pid); - - process.stdout.transform(ByteToLineTransformer()).listen(_handleStdout); - process.stderr.listen(_handleStderr); - unawaited(process.exitCode.then(_handleExitCode)); - } - /// restart is called by the client when the user invokes a restart (for example with the button on the debug toolbar). /// /// For Flutter, we handle this ourselves be sending a Hot Restart request @@ -379,7 +286,7 @@ class FlutterDebugAdapter extends DartDebugAdapter? params, { bool failSilently = true, }) async { - final Process? process = _process; + final Process? process = this.process; if (process == null) { if (failSilently) { @@ -416,16 +323,16 @@ class FlutterDebugAdapter extends DartDebugAdapter(>[ - sendFlutterRequest(method, {'appId': appId}), - _process?.exitCode ?? Future.value(), + sendFlutterRequest(method, {'appId': _appId}), + process?.exitCode ?? Future.value(), ]); } terminatePids(ProcessSignal.sigterm); - await _process?.exitCode; + await process?.exitCode; } /// Connects to the VM Service if the app.started event has fired, and a VM Service URI is available. @@ -451,21 +358,23 @@ class FlutterDebugAdapter extends DartDebugAdapter params) { - appId = params['appId'] as String?; - if(appId == null) { + _appId = params['appId'] as String?; + if (_appId == null) { throw DebugAdapterException('Unexpected null `appId` in app.start event'); } } /// Handles the app.started event from Flutter. Future _handleAppStarted() async { - appStartedCompleter.complete(); + _appStartedCompleter.complete(); // Send a custom event so the editor knows the app has started. // // This may be useful when there's no VM Service (for example Profile mode) // but the editor still wants to know that startup has finished. - await debuggerInitialized; // Ensure we're fully initialized before sending. + if (enableDebugger) { + await debuggerInitialized; // Ensure we're fully initialized before sending. + } sendEvent( RawEventBody({}), eventType: 'flutter.appStarted', @@ -491,13 +400,14 @@ class FlutterDebugAdapter extends DartDebugAdapter data) { + @override + void handleStderr(List data) { logger?.call('stderr: $data'); sendOutput('stderr', utf8.decode(data)); } /// Handles stdout from the `flutter run --machine` process, decoding the JSON and calling the appropriate handlers. - void _handleStdout(String data) { + @override + void handleStdout(String data) { // Output intended for us to parse is JSON wrapped in brackets: // [{"event":"app.foo","params":{"bar":"baz"}}] // However, it's also possible a user printed things that look a little like @@ -624,7 +536,7 @@ class FlutterDebugAdapter extends DartDebugAdapter{ - 'appId': appId, + 'appId': _appId, 'fullRestart': fullRestart, 'pause': enableDebugger, 'reason': reason, @@ -633,8 +545,7 @@ class FlutterDebugAdapter extends DartDebugAdapter + with PidTracker { + FlutterBaseDebugAdapter( + super.channel, { + required this.fileSystem, + required this.platform, + super.ipv6, + this.enableFlutterDds = true, + super.enableAuthCodes, + super.logger, + super.onError, + }) : flutterSdkRoot = Cache.flutterRoot!, + // Always disable in the DAP layer as it's handled in the spawned + // 'flutter' process. + super(enableDds: false) { + configureOrgDartlangSdkMappings(); + } + + FileSystem fileSystem; + Platform platform; + Process? process; + + final String flutterSdkRoot; + + /// Whether DDS should be enabled in the Flutter process. + /// + /// We never enable DDS in the DAP process for Flutter, so this value is not + /// the same as what is passed to the base class, which is always provided 'false'. + final bool enableFlutterDds; + + @override + final FlutterLaunchRequestArguments Function(Map obj) + parseLaunchArgs = FlutterLaunchRequestArguments.fromJson; + + @override + final FlutterAttachRequestArguments Function(Map obj) + parseAttachArgs = FlutterAttachRequestArguments.fromJson; + + /// Whether the VM Service closing should be used as a signal to terminate the debug session. + /// + /// Since we always have a process for Flutter (whether run or attach) we'll + /// always use its termination instead, so this is always false. + @override + bool get terminateOnVmServiceClose => false; + + /// Whether or not the user requested debugging be enabled. + /// + /// For debugging to be enabled, the user must have chosen "Debug" (and not + /// "Run") in the editor (which maps to the DAP `noDebug` field). + bool get enableDebugger { + final DartCommonLaunchAttachRequestArguments args = this.args; + if (args is FlutterLaunchRequestArguments) { + // Invert DAP's noDebug flag, treating it as false (so _do_ debug) if not + // provided. + return !(args.noDebug ?? false); + } + + // Otherwise (attach), always debug. + return true; + } + + void configureOrgDartlangSdkMappings() { + /// When a user navigates into 'dart:xxx' sources in their editor (via the + /// analysis server) they will land in flutter_sdk/bin/cache/pkg/sky_engine. + /// + /// The running VM knows nothing about these paths and will resolve these + /// libraries to 'org-dartlang-sdk://' URIs. We need to map between these + /// to ensure that if a user puts a breakpoint inside sky_engine the VM can + /// apply it to the correct place and once hit, we can navigate the user + /// back to the correct file on their disk. + /// + /// The mapping is handled by the base adapter but we need to override the + /// paths to match the layout used by Flutter. + /// + /// In future this might become unnecessary if + /// https://github.com/dart-lang/sdk/issues/48435 is implemented. Until + /// then, providing these mappings improves the debugging experience. + + // Clear original Dart SDK mappings because they're not valid here. + orgDartlangSdkMappings.clear(); + + // 'dart:ui' maps to /flutter/lib/ui + final String flutterRoot = fileSystem.path.join(flutterSdkRoot, 'bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui'); + orgDartlangSdkMappings[flutterRoot] = Uri.parse('org-dartlang-sdk:///flutter/lib/ui'); + + // The rest of the Dart SDK maps to /third_party/dart/sdk + final String dartRoot = fileSystem.path.join(flutterSdkRoot, 'bin', 'cache', 'pkg', 'sky_engine'); + orgDartlangSdkMappings[dartRoot] = Uri.parse('org-dartlang-sdk:///third_party/dart/sdk'); + } + + @override + Future debuggerConnected(vm.VM vmInfo) async { + // Usually we'd capture the pid from the VM here and record it for + // terminating, however for Flutter apps it may be running on a remote + // device so it's not valid to terminate a process with that pid locally. + // For attach, pids should never be collected as terminateRequest() should + // not terminate the debugee. + } + + /// Called by [disconnectRequest] to request that we forcefully shut down the app being run (or in the case of an attach, disconnect). + /// + /// Client IDEs/editors should send a terminateRequest before a + /// disconnectRequest to allow a graceful shutdown. This method must terminate + /// quickly and therefore may leave orphaned processes. + @override + Future disconnectImpl() async { + if (isAttach) { + await preventBreakingAndResume(); + } + terminatePids(ProcessSignal.sigkill); + } + + Future launchAsProcess({ + required String executable, + required List processArgs, + required Map? env, + }) async { + final Process process = await ( + String executable, + List processArgs, { + required Map? env, + }) async { + logger?.call('Spawning $executable with $processArgs in ${args.cwd}'); + final Process process = await Process.start( + executable, + processArgs, + workingDirectory: args.cwd, + environment: env, + ); + pidsToTerminate.add(process.pid); + return process; + }(executable, processArgs, env: env); + this.process = process; + + process.stdout.transform(ByteToLineTransformer()).listen(handleStdout); + process.stderr.listen(handleStderr); + unawaited(process.exitCode.then(handleExitCode)); + } + + void handleExitCode(int code); + void handleStderr(List data); + void handleStdout(String data); +} diff --git a/packages/flutter_tools/lib/src/debug_adapters/flutter_test_adapter.dart b/packages/flutter_tools/lib/src/debug_adapters/flutter_test_adapter.dart index 3fbc3cd32c0..3072d1958b1 100644 --- a/packages/flutter_tools/lib/src/debug_adapters/flutter_test_adapter.dart +++ b/packages/flutter_tools/lib/src/debug_adapters/flutter_test_adapter.dart @@ -6,64 +6,25 @@ import 'dart:async'; import 'dart:math' as math; import 'package:dds/dap.dart' hide PidTracker; -import 'package:meta/meta.dart'; -import 'package:vm_service/vm_service.dart' as vm; -import '../base/file_system.dart'; import '../base/io.dart'; -import '../base/platform.dart'; import '../cache.dart'; import '../convert.dart'; import 'flutter_adapter_args.dart'; -import 'mixins.dart'; +import 'flutter_base_adapter.dart'; /// A DAP Debug Adapter for running and debugging Flutter tests. -class FlutterTestDebugAdapter extends DartDebugAdapter - with PidTracker, FlutterAdapter, TestAdapter { +class FlutterTestDebugAdapter extends FlutterBaseDebugAdapter with TestAdapter { FlutterTestDebugAdapter( super.channel, { - required this.fileSystem, - required this.platform, + required super.fileSystem, + required super.platform, super.ipv6, - bool enableDds = true, + super.enableFlutterDds = true, super.enableAuthCodes, super.logger, super.onError, - }) : _enableDds = enableDds, - flutterSdkRoot = Cache.flutterRoot!, - // Always disable in the DAP layer as it's handled in the spawned - // 'flutter' process. - super(enableDds: false) { - configureOrgDartlangSdkMappings(); - } - - @override - FileSystem fileSystem; - Platform platform; - Process? _process; - - @override - final String flutterSdkRoot; - - /// Whether DDS should be enabled in the Flutter process. - /// - /// We never enable DDS in the DAP process for Flutter, so this value is not - /// the same as what is passed to the base class, which is always provided 'false'. - final bool _enableDds; - - @override - final FlutterLaunchRequestArguments Function(Map obj) - parseLaunchArgs = FlutterLaunchRequestArguments.fromJson; - - @override - final FlutterAttachRequestArguments Function(Map obj) - parseAttachArgs = FlutterAttachRequestArguments.fromJson; - - /// Whether the VM Service closing should be used as a signal to terminate the debug session. - /// - /// Since we do not support attaching for tests, this is always false. - @override - bool get terminateOnVmServiceClose => false; + }); /// Called by [attachRequest] to request that we actually connect to the app to be debugged. @override @@ -72,45 +33,6 @@ class FlutterTestDebugAdapter extends DartDebugAdapter debuggerConnected(vm.VM vmInfo) async { - // Capture the PID from the VM Service so that we can terminate it when - // cleaning up. Terminating the process might not be enough as it could be - // just a shell script (e.g. pub on Windows) and may not pass the - // signal on correctly. - // See: https://github.com/Dart-Code/Dart-Code/issues/907 - final int? pid = vmInfo.pid; - if (pid != null) { - pidsToTerminate.add(pid); - } - } - - /// Called by [disconnectRequest] to request that we forcefully shut down the app being run (or in the case of an attach, disconnect). - /// - /// Client IDEs/editors should send a terminateRequest before a - /// disconnectRequest to allow a graceful shutdown. This method must terminate - /// quickly and therefore may leave orphaned processes. - @override - Future disconnectImpl() async { - terminatePids(ProcessSignal.sigkill); - } - - /// Whether or not the user requested debugging be enabled. - /// - /// For debugging to be enabled, the user must have chosen "Debug" (and not - /// "Run") in the editor (which maps to the DAP `noDebug` field). - bool get enableDebugger { - final DartCommonLaunchAttachRequestArguments args = this.args; - if (args is FlutterLaunchRequestArguments) { - // Invert DAP's noDebug flag, treating it as false (so _do_ debug) if not - // provided. - return !(args.noDebug ?? false); - } - - // Otherwise (attach), always debug. - return true; - } - /// Called by [launchRequest] to request that we actually start the tests to be run/debugged. /// /// For debugging, this should start paused, connect to the VM Service, set @@ -125,7 +47,7 @@ class FlutterTestDebugAdapter extends DartDebugAdapter toolArgs = [ 'test', '--machine', - if (!_enableDds) '--no-dds', + if (!enableFlutterDds) '--no-dds', if (debug) '--start-paused', ]; @@ -155,36 +77,16 @@ class FlutterTestDebugAdapter extends DartDebugAdapter launchAsProcess({ - required String executable, - required List processArgs, - required Map? env, - }) async { - logger?.call('Spawning $executable with $processArgs in ${args.cwd}'); - final Process process = await Process.start( - executable, - processArgs, - workingDirectory: args.cwd, - environment: env, - ); - _process = process; - pidsToTerminate.add(process.pid); - - process.stdout.transform(ByteToLineTransformer()).listen(_handleStdout); - process.stderr.listen(_handleStderr); - unawaited(process.exitCode.then(_handleExitCode)); - } - /// Called by [terminateRequest] to request that we gracefully shut down the app being run (or in the case of an attach, disconnect). @override Future terminateImpl() async { terminatePids(ProcessSignal.sigterm); - await _process?.exitCode; + await process?.exitCode; } /// Handles the Flutter process exiting, terminating the debug session if it has not already begun terminating. - void _handleExitCode(int code) { + @override + void handleExitCode(int code) { final String codeSuffix = code == 0 ? '' : ' ($code)'; logger?.call('Process exited ($code)'); handleSessionTerminate(codeSuffix); @@ -202,13 +104,15 @@ class FlutterTestDebugAdapter extends DartDebugAdapter data) { + @override + void handleStderr(List data) { logger?.call('stderr: $data'); sendOutput('stderr', utf8.decode(data)); } /// Handles stdout from the `flutter test --machine` process, decoding the JSON and calling the appropriate handlers. - void _handleStdout(String data) { + @override + void handleStdout(String data) { // Output to stdout from `flutter test --machine` is either: // 1. JSON output from flutter_tools (eg. "test.startedProcess") which is // wrapped in [] brackets and has an event/params. diff --git a/packages/flutter_tools/lib/src/debug_adapters/mixins.dart b/packages/flutter_tools/lib/src/debug_adapters/mixins.dart index 6a0c778790d..fd4e4c366d1 100644 --- a/packages/flutter_tools/lib/src/debug_adapters/mixins.dart +++ b/packages/flutter_tools/lib/src/debug_adapters/mixins.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../base/file_system.dart'; import '../base/io.dart'; /// A mixin for tracking additional PIDs that can be shut down at the end of a debug session. @@ -23,38 +22,3 @@ mixin PidTracker { pidsToTerminate.forEach(signal.send); } } - -mixin FlutterAdapter { - Map get orgDartlangSdkMappings; - String get flutterSdkRoot; - FileSystem get fileSystem; - - void configureOrgDartlangSdkMappings() { - /// When a user navigates into 'dart:xxx' sources in their editor (via the - /// analysis server) they will land in flutter_sdk/bin/cache/pkg/sky_engine. - /// - /// The running VM knows nothing about these paths and will resolve these - /// libraries to 'org-dartlang-sdk://' URIs. We need to map between these - /// to ensure that if a user puts a breakpoint inside sky_engine the VM can - /// apply it to the correct place and once hit, we can navigate the user - /// back to the correct file on their disk. - /// - /// The mapping is handled by the base adapter but we need to override the - /// paths to match the layout used by Flutter. - /// - /// In future this might become unnecessary if - /// https://github.com/dart-lang/sdk/issues/48435 is implemented. Until - /// then, providing these mappings improves the debugging experience. - - // Clear original Dart SDK mappings because they're not valid here. - orgDartlangSdkMappings.clear(); - - // 'dart:ui' maps to /flutter/lib/ui - final String flutterRoot = fileSystem.path.join(flutterSdkRoot, 'bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui'); - orgDartlangSdkMappings[flutterRoot] = Uri.parse('org-dartlang-sdk:///flutter/lib/ui'); - - // The rest of the Dart SDK maps to /third_party/dart/sdk - final String dartRoot = fileSystem.path.join(flutterSdkRoot, 'bin', 'cache', 'pkg', 'sky_engine'); - orgDartlangSdkMappings[dartRoot] = Uri.parse('org-dartlang-sdk:///third_party/dart/sdk'); - } -} diff --git a/packages/flutter_tools/lib/src/debug_adapters/server.dart b/packages/flutter_tools/lib/src/debug_adapters/server.dart index 7c72307dbbb..d3a4df68024 100644 --- a/packages/flutter_tools/lib/src/debug_adapters/server.dart +++ b/packages/flutter_tools/lib/src/debug_adapters/server.dart @@ -38,7 +38,7 @@ class DapServer { fileSystem: fileSystem, platform: platform, ipv6: ipv6, - enableDds: enableDds, + enableFlutterDds: enableDds, enableAuthCodes: enableAuthCodes, logger: logger, onError: onError, @@ -47,7 +47,7 @@ class DapServer { channel, fileSystem: fileSystem, platform: platform, - enableDds: enableDds, + enableFlutterDds: enableDds, enableAuthCodes: enableAuthCodes, logger: logger, onError: onError, diff --git a/packages/flutter_tools/test/general.shard/dap/mocks.dart b/packages/flutter_tools/test/general.shard/dap/mocks.dart index 2cb8475b817..e53296153a3 100644 --- a/packages/flutter_tools/test/general.shard/dap/mocks.dart +++ b/packages/flutter_tools/test/general.shard/dap/mocks.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:dds/dap.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/debug_adapters/flutter_adapter.dart'; import 'package:flutter_tools/src/debug_adapters/flutter_test_adapter.dart'; @@ -59,14 +60,29 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { this.processArgs = processArgs; this.env = env; - // Pretend we launched the app and got the app.started event so that - // launchRequest will complete. + // Simulate the app starting by triggering handling of events that Flutter + // would usually write to stdout. if (simulateAppStarted) { - appId = 'TEST'; - appStartedCompleter.complete(); + simulateStdoutMessage({ + 'event': 'app.started', + }); + simulateStdoutMessage({ + 'event': 'app.start', + 'params': { + 'appId': 'TEST', + } + }); } } + /// Simulates a message emitted by the `flutter run` process by directly + /// calling the debug adapters [handleStdout] method. + void simulateStdoutMessage(Map message) { + // Messages are wrapped in a list because Flutter only processes messages + // wrapped in brackets. + handleStdout(jsonEncode([message])); + } + @override Future sendFlutterRequest( String method,