Unverified Commit 9f9010f5 authored by Danny Tuppeny's avatar Danny Tuppeny Committed by GitHub

[flutter_tools] Update DAP progress when waiting for Dart Debug extension connection (#116892)

Fixes https://github.com/Dart-Code/Dart-Code/issues/4293.
parent 3eefb7af
...@@ -36,6 +36,11 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { ...@@ -36,6 +36,11 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
/// The appId of the current running Flutter app. /// The appId of the current running Flutter app.
String? _appId; String? _appId;
/// A progress reporter for the applications launch progress.
///
/// `null` if a launch is not in progress (or has completed).
DapProgressReporter? launchProgress;
/// The ID to use for the next request sent to the Flutter run daemon. /// The ID to use for the next request sent to the Flutter run daemon.
int _flutterRequestId = 1; int _flutterRequestId = 1;
...@@ -123,12 +128,11 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { ...@@ -123,12 +128,11 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
Future<void> attachImpl() async { Future<void> attachImpl() async {
final FlutterAttachRequestArguments args = this.args as FlutterAttachRequestArguments; final FlutterAttachRequestArguments args = this.args as FlutterAttachRequestArguments;
final DapProgressReporter progress = startProgressNotification( launchProgress = startProgressNotification(
'launch', 'launch',
'Flutter', 'Flutter',
message: 'Attaching…', message: 'Attaching…',
); );
unawaited(_appStartedCompleter.future.then((_) => progress.end()));
final String? vmServiceUri = args.vmServiceUri; final String? vmServiceUri = args.vmServiceUri;
final List<String> toolArgs = <String>[ final List<String> toolArgs = <String>[
...@@ -230,12 +234,11 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { ...@@ -230,12 +234,11 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
Future<void> launchImpl() async { Future<void> launchImpl() async {
final FlutterLaunchRequestArguments args = this.args as FlutterLaunchRequestArguments; final FlutterLaunchRequestArguments args = this.args as FlutterLaunchRequestArguments;
final DapProgressReporter progress = startProgressNotification( launchProgress = startProgressNotification(
'launch', 'launch',
'Flutter', 'Flutter',
message: 'Launching…', message: 'Launching…',
); );
unawaited(_appStartedCompleter.future.then((_) => progress.end()));
final List<String> toolArgs = <String>[ final List<String> toolArgs = <String>[
'run', 'run',
...@@ -398,6 +401,8 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { ...@@ -398,6 +401,8 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
/// Handles the app.started event from Flutter. /// Handles the app.started event from Flutter.
Future<void> _handleAppStarted() async { Future<void> _handleAppStarted() async {
launchProgress?.end();
launchProgress = null;
_appStartedCompleter.complete(); _appStartedCompleter.complete();
// Send a custom event so the editor knows the app has started. // Send a custom event so the editor knows the app has started.
...@@ -591,6 +596,16 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { ...@@ -591,6 +596,16 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
// If the output wasn't valid JSON, it was standard stdout that should // If the output wasn't valid JSON, it was standard stdout that should
// be passed through to the user. // be passed through to the user.
sendOutput(outputCategory, data); sendOutput(outputCategory, data);
// Detect if the output contains a prompt about using the Dart Debug
// extension and also update the progress notification to make it clearer
// we're waiting for the user to do something.
if (data.contains('Waiting for connection from Dart debug extension')) {
launchProgress?.update(
message: 'Please click the Dart Debug extension button in the spawned browser window',
);
}
return; return;
} }
......
...@@ -147,6 +147,47 @@ void main() { ...@@ -147,6 +147,47 @@ void main() {
expect(adapter.dapToFlutterRequests, isNot(contains('app.stop'))); expect(adapter.dapToFlutterRequests, isNot(contains('app.stop')));
}); });
test('includes Dart Debug extension progress update', () async {
final MockFlutterDebugAdapter adapter = MockFlutterDebugAdapter(
fileSystem: MemoryFileSystem.test(style: fsStyle),
platform: platform,
preAppStart: (MockFlutterDebugAdapter adapter) {
adapter.simulateRawStdout('Waiting for connection from Dart debug extension…');
}
);
final Completer<void> responseCompleter = Completer<void>();
final FlutterLaunchRequestArguments args = FlutterLaunchRequestArguments(
cwd: '/project',
program: 'foo.dart',
);
// Begin listening for progress events up until `progressEnd` (but don't await yet).
final Future<List<List<Object?>>> progressEventsFuture =
adapter.dapToClientProgressEvents
.takeWhile((Map<String, Object?> message) => message['event'] != 'progressEnd')
.map((Map<String, Object?> message) => <Object?>[message['event'], (message['body']! as Map<String, Object?>)['message']])
.toList();
// Initialize with progress support.
await adapter.initializeRequest(
MockRequest(),
InitializeRequestArguments(adapterID: 'test', supportsProgressReporting: true, ),
(_) {},
);
await adapter.configurationDoneRequest(MockRequest(), null, () {});
await adapter.launchRequest(MockRequest(), args, responseCompleter.complete);
await responseCompleter.future;
// Ensure we got the expected events prior to the
final List<List<Object?>> progressEvents = await progressEventsFuture;
expect(progressEvents, containsAllInOrder(<List<String>>[
<String>['progressStart', 'Launching…'],
<String>['progressUpdate', 'Please click the Dart Debug extension button in the spawned browser window'],
// progressEnd isn't included because we used takeWhile to stop when it arrived above.
]));
});
}); });
group('attachRequest', () { group('attachRequest', () {
...@@ -221,6 +262,11 @@ void main() { ...@@ -221,6 +262,11 @@ void main() {
platform: platform, platform: platform,
); );
// Start listening for the forwarded event (don't await it yet, it won't
// be triggered until the call below).
final Future<Map<String, Object?>> forwardedEvent = adapter.dapToClientMessages
.firstWhere((Map<String, Object?> data) => data['event'] == 'flutter.forwardedEvent');
// Simulate Flutter asking for a URL to be launched. // Simulate Flutter asking for a URL to be launched.
adapter.simulateStdoutMessage(<String, Object?>{ adapter.simulateStdoutMessage(<String, Object?>{
'event': 'app.webLaunchUrl', 'event': 'app.webLaunchUrl',
...@@ -230,11 +276,8 @@ void main() { ...@@ -230,11 +276,8 @@ void main() {
} }
}); });
// Allow the handler to be processed. // Wait for the forwarded event.
await pumpEventQueue(times: 5000); final Map<String, Object?> message = await forwardedEvent;
// Find the forwarded event.
final Map<String, Object?> message = adapter.dapToClientMessages.singleWhere((Map<String, Object?> data) => data['event'] == 'flutter.forwardedEvent');
// Ensure the body of the event matches the original event sent by Flutter. // Ensure the body of the event matches the original event sent by Flutter.
expect(message['body'], <String, Object?>{ expect(message['body'], <String, Object?>{
'event': 'app.webLaunchUrl', 'event': 'app.webLaunchUrl',
......
...@@ -18,6 +18,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { ...@@ -18,6 +18,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
required FileSystem fileSystem, required FileSystem fileSystem,
required Platform platform, required Platform platform,
bool simulateAppStarted = true, bool simulateAppStarted = true,
FutureOr<void> Function(MockFlutterDebugAdapter adapter)? preAppStart,
}) { }) {
final StreamController<List<int>> stdinController = StreamController<List<int>>(); final StreamController<List<int>> stdinController = StreamController<List<int>>();
final StreamController<List<int>> stdoutController = StreamController<List<int>>(); final StreamController<List<int>> stdoutController = StreamController<List<int>>();
...@@ -30,6 +31,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { ...@@ -30,6 +31,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
fileSystem: fileSystem, fileSystem: fileSystem,
platform: platform, platform: platform,
simulateAppStarted: simulateAppStarted, simulateAppStarted: simulateAppStarted,
preAppStart: preAppStart,
); );
} }
...@@ -39,6 +41,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { ...@@ -39,6 +41,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
required super.fileSystem, required super.fileSystem,
required super.platform, required super.platform,
this.simulateAppStarted = true, this.simulateAppStarted = true,
this.preAppStart,
}) { }) {
clientChannel.listen((ProtocolMessage message) { clientChannel.listen((ProtocolMessage message) {
_handleDapToClientMessage(message); _handleDapToClientMessage(message);
...@@ -48,13 +51,24 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { ...@@ -48,13 +51,24 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
int _seq = 1; int _seq = 1;
final ByteStreamServerChannel clientChannel; final ByteStreamServerChannel clientChannel;
final bool simulateAppStarted; final bool simulateAppStarted;
final FutureOr<void> Function(MockFlutterDebugAdapter adapter)? preAppStart;
late String executable; late String executable;
late List<String> processArgs; late List<String> processArgs;
late Map<String, String>? env; late Map<String, String>? env;
/// A list of all messages sent from the adapter back to the client. final StreamController<Map<String, Object?>> _dapToClientMessagesController = StreamController<Map<String, Object?>>.broadcast();
final List<Map<String, Object?>> dapToClientMessages = <Map<String, Object?>>[];
/// A stream of all messages sent from the adapter back to the client.
Stream<Map<String, Object?>> get dapToClientMessages => _dapToClientMessagesController.stream;
/// A stream of all progress events sent from the adapter back to the client.
Stream<Map<String, Object?>> get dapToClientProgressEvents {
const List<String> progressEventTypes = <String>['progressStart', 'progressUpdate', 'progressEnd'];
return dapToClientMessages
.where((Map<String, Object?> message) => progressEventTypes.contains(message['event'] as String?));
}
/// A list of all messages sent from the adapter to the `flutter run` processes `stdin`. /// A list of all messages sent from the adapter to the `flutter run` processes `stdin`.
final List<Map<String, Object?>> dapToFlutterMessages = <Map<String, Object?>>[]; final List<Map<String, Object?>> dapToFlutterMessages = <Map<String, Object?>>[];
...@@ -79,6 +93,8 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { ...@@ -79,6 +93,8 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
this.processArgs = processArgs; this.processArgs = processArgs;
this.env = env; this.env = env;
await preAppStart?.call(this);
// Simulate the app starting by triggering handling of events that Flutter // Simulate the app starting by triggering handling of events that Flutter
// would usually write to stdout. // would usually write to stdout.
if (simulateAppStarted) { if (simulateAppStarted) {
...@@ -96,7 +112,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { ...@@ -96,7 +112,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
/// Handles messages sent from the debug adapter back to the client. /// Handles messages sent from the debug adapter back to the client.
void _handleDapToClientMessage(ProtocolMessage message) { void _handleDapToClientMessage(ProtocolMessage message) {
dapToClientMessages.add(message.toJson()); _dapToClientMessagesController.add(message.toJson());
// Pretend to be the client, delegating any reverse-requests to the relevant // Pretend to be the client, delegating any reverse-requests to the relevant
// handler that is provided by the test. // handler that is provided by the test.
...@@ -131,12 +147,22 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { ...@@ -131,12 +147,22 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
/// Simulates a message emitted by the `flutter run` process by directly /// Simulates a message emitted by the `flutter run` process by directly
/// calling the debug adapters [handleStdout] method. /// calling the debug adapters [handleStdout] method.
///
/// Use [simulateRawStdout] to simulate non-daemon text output.
void simulateStdoutMessage(Map<String, Object?> message) { void simulateStdoutMessage(Map<String, Object?> message) {
// Messages are wrapped in a list because Flutter only processes messages // Messages are wrapped in a list because Flutter only processes messages
// wrapped in brackets. // wrapped in brackets.
handleStdout(jsonEncode(<Object?>[message])); handleStdout(jsonEncode(<Object?>[message]));
} }
/// Simulates a string emitted by the `flutter run` process by directly
/// calling the debug adapters [handleStdout] method.
///
/// Use [simulateStdoutMessage] to simulate a daemon JSON message.
void simulateRawStdout(String output) {
handleStdout(output);
}
@override @override
void sendFlutterMessage(Map<String, Object?> message) { void sendFlutterMessage(Map<String, Object?> message) {
dapToFlutterMessages.add(message); dapToFlutterMessages.add(message);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment