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 {
/// The appId of the current running Flutter app.
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.
int _flutterRequestId = 1;
......@@ -123,12 +128,11 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
Future<void> attachImpl() async {
final FlutterAttachRequestArguments args = this.args as FlutterAttachRequestArguments;
final DapProgressReporter progress = startProgressNotification(
launchProgress = startProgressNotification(
'launch',
'Flutter',
message: 'Attaching…',
);
unawaited(_appStartedCompleter.future.then((_) => progress.end()));
final String? vmServiceUri = args.vmServiceUri;
final List<String> toolArgs = <String>[
......@@ -230,12 +234,11 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
Future<void> launchImpl() async {
final FlutterLaunchRequestArguments args = this.args as FlutterLaunchRequestArguments;
final DapProgressReporter progress = startProgressNotification(
launchProgress = startProgressNotification(
'launch',
'Flutter',
message: 'Launching…',
);
unawaited(_appStartedCompleter.future.then((_) => progress.end()));
final List<String> toolArgs = <String>[
'run',
......@@ -398,6 +401,8 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
/// Handles the app.started event from Flutter.
Future<void> _handleAppStarted() async {
launchProgress?.end();
launchProgress = null;
_appStartedCompleter.complete();
// Send a custom event so the editor knows the app has started.
......@@ -591,6 +596,16 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
// If the output wasn't valid JSON, it was standard stdout that should
// be passed through to the user.
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;
}
......
......@@ -147,6 +147,47 @@ void main() {
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', () {
......@@ -221,6 +262,11 @@ void main() {
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.
adapter.simulateStdoutMessage(<String, Object?>{
'event': 'app.webLaunchUrl',
......@@ -230,11 +276,8 @@ void main() {
}
});
// Allow the handler to be processed.
await pumpEventQueue(times: 5000);
// Find the forwarded event.
final Map<String, Object?> message = adapter.dapToClientMessages.singleWhere((Map<String, Object?> data) => data['event'] == 'flutter.forwardedEvent');
// Wait for the forwarded event.
final Map<String, Object?> message = await forwardedEvent;
// Ensure the body of the event matches the original event sent by Flutter.
expect(message['body'], <String, Object?>{
'event': 'app.webLaunchUrl',
......
......@@ -18,6 +18,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
required FileSystem fileSystem,
required Platform platform,
bool simulateAppStarted = true,
FutureOr<void> Function(MockFlutterDebugAdapter adapter)? preAppStart,
}) {
final StreamController<List<int>> stdinController = StreamController<List<int>>();
final StreamController<List<int>> stdoutController = StreamController<List<int>>();
......@@ -30,6 +31,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
fileSystem: fileSystem,
platform: platform,
simulateAppStarted: simulateAppStarted,
preAppStart: preAppStart,
);
}
......@@ -39,6 +41,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
required super.fileSystem,
required super.platform,
this.simulateAppStarted = true,
this.preAppStart,
}) {
clientChannel.listen((ProtocolMessage message) {
_handleDapToClientMessage(message);
......@@ -48,13 +51,24 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
int _seq = 1;
final ByteStreamServerChannel clientChannel;
final bool simulateAppStarted;
final FutureOr<void> Function(MockFlutterDebugAdapter adapter)? preAppStart;
late String executable;
late List<String> processArgs;
late Map<String, String>? env;
/// A list of all messages sent from the adapter back to the client.
final List<Map<String, Object?>> dapToClientMessages = <Map<String, Object?>>[];
final StreamController<Map<String, Object?>> _dapToClientMessagesController = StreamController<Map<String, Object?>>.broadcast();
/// 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`.
final List<Map<String, Object?>> dapToFlutterMessages = <Map<String, Object?>>[];
......@@ -79,6 +93,8 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
this.processArgs = processArgs;
this.env = env;
await preAppStart?.call(this);
// Simulate the app starting by triggering handling of events that Flutter
// would usually write to stdout.
if (simulateAppStarted) {
......@@ -96,7 +112,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
/// Handles messages sent from the debug adapter back to the client.
void _handleDapToClientMessage(ProtocolMessage message) {
dapToClientMessages.add(message.toJson());
_dapToClientMessagesController.add(message.toJson());
// Pretend to be the client, delegating any reverse-requests to the relevant
// handler that is provided by the test.
......@@ -131,12 +147,22 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
/// Simulates a message emitted by the `flutter run` process by directly
/// calling the debug adapters [handleStdout] method.
///
/// Use [simulateRawStdout] to simulate non-daemon text output.
void simulateStdoutMessage(Map<String, Object?> message) {
// Messages are wrapped in a list because Flutter only processes messages
// wrapped in brackets.
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
void sendFlutterMessage(Map<String, Object?> 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