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 1f1f79af85..3d05858095 100644 --- a/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart +++ b/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart @@ -140,6 +140,13 @@ class FlutterDebugAdapter extends DartDebugAdapter attachImpl() async { final FlutterAttachRequestArguments args = this.args as FlutterAttachRequestArguments; + final DapProgressReporter progress = startProgressNotification( + 'launch', + 'Flutter', + message: 'Attaching…', + ); + unawaited(appStartedCompleter.future.then((_) => progress.end())); + final String? vmServiceUri = args.vmServiceUri; final List toolArgs = [ 'attach', @@ -255,6 +262,13 @@ class FlutterDebugAdapter extends DartDebugAdapter launchImpl() async { final FlutterLaunchRequestArguments args = this.args as FlutterLaunchRequestArguments; + final DapProgressReporter progress = startProgressNotification( + 'launch', + 'Flutter', + message: 'Launching…', + ); + unawaited(appStartedCompleter.future.then((_) => progress.end())); + final List toolArgs = [ 'run', '--machine', @@ -593,6 +607,14 @@ class FlutterDebugAdapter extends DartDebugAdapter{ 'appId': appId, @@ -605,6 +627,9 @@ class FlutterDebugAdapter extends DartDebugAdapter>[ + dap.client.stdoutOutput.firstWhere((String output) => output.startsWith('topLevelFunction')), + dap.client.initialize(supportsProgressReporting: true), + dap.client.launch( + cwd: project.dir.path, + noDebug: true, + toolArgs: ['-d', 'flutter-tester'], + ), + ], eagerError: true); + + // Capture progress events during a reload. + final Future> progressEventsFuture = dap.client.progressEvents().toList(); + await dap.client.hotReload(); + await dap.client.terminate(); + + // Verify the progress events. + final List progressEvents = await progressEventsFuture; + expect(progressEvents, hasLength(2)); + + final List eventKinds = progressEvents.map((Event event) => event.event).toList(); + expect(eventKinds, ['progressStart', 'progressEnd']); + + final List> eventBodies = progressEvents.map((Event event) => event.body).cast>().toList(); + final ProgressStartEventBody start = ProgressStartEventBody.fromMap(eventBodies[0]); + final ProgressEndEventBody end = ProgressEndEventBody.fromMap(eventBodies[1]); + expect(start.progressId, isNotNull); + expect(start.title, 'Flutter'); + expect(start.message, 'Hot reloading…'); + expect(end.progressId, start.progressId); + expect(end.message, isNull); + }); + testWithoutContext('can hot restart', () async { final BasicProject project = BasicProject(); await project.setUpIn(tempDir); @@ -255,6 +292,43 @@ void main() { await dap.client.terminate(); }); + testWithoutContext('sends progress notifications during hot restart', () async { + final BasicProject project = BasicProject(); + await project.setUpIn(tempDir); + + // Launch the app and wait for it to print "topLevelFunction". + await Future.wait(>[ + dap.client.stdoutOutput.firstWhere((String output) => output.startsWith('topLevelFunction')), + dap.client.initialize(supportsProgressReporting: true), + dap.client.launch( + cwd: project.dir.path, + noDebug: true, + toolArgs: ['-d', 'flutter-tester'], + ), + ], eagerError: true); + + // Capture progress events during a restart. + final Future> progressEventsFuture = dap.client.progressEvents().toList(); + await dap.client.hotRestart(); + await dap.client.terminate(); + + // Verify the progress events. + final List progressEvents = await progressEventsFuture; + expect(progressEvents, hasLength(2)); + + final List eventKinds = progressEvents.map((Event event) => event.event).toList(); + expect(eventKinds, ['progressStart', 'progressEnd']); + + final List> eventBodies = progressEvents.map((Event event) => event.body).cast>().toList(); + final ProgressStartEventBody start = ProgressStartEventBody.fromMap(eventBodies[0]); + final ProgressEndEventBody end = ProgressEndEventBody.fromMap(eventBodies[1]); + expect(start.progressId, isNotNull); + expect(start.title, 'Flutter'); + expect(start.message, 'Hot restarting…'); + expect(end.progressId, start.progressId); + expect(end.message, isNull); + }); + testWithoutContext('can hot restart when exceptions occur on outgoing isolates', () async { final BasicProjectThatThrows project = BasicProjectThatThrows(); await project.setUpIn(tempDir); diff --git a/packages/flutter_tools/test/integration.shard/debug_adapter/test_client.dart b/packages/flutter_tools/test/integration.shard/debug_adapter/test_client.dart index e1f823ad47..1bb15b3c97 100644 --- a/packages/flutter_tools/test/integration.shard/debug_adapter/test_client.dart +++ b/packages/flutter_tools/test/integration.shard/debug_adapter/test_client.dart @@ -83,6 +83,12 @@ class DapTestClient { return _eventController.stream.where((Event e) => e.event == event); } + /// Returns a stream of progress events. + Stream progressEvents() { + const Set progressEvents = {'progressStart', 'progressUpdate', 'progressEnd'}; + return _eventController.stream.where((Event e) => progressEvents.contains(e.event)); + } + /// Returns a stream of custom 'dart.serviceExtensionAdded' events. Stream> get serviceExtensionAddedEvents => events('dart.serviceExtensionAdded') @@ -116,12 +122,14 @@ class DapTestClient { Future initialize({ String exceptionPauseMode = 'None', bool? supportsRunInTerminalRequest, + bool? supportsProgressReporting, }) async { final List responses = await Future.wait(>[ event('initialized'), sendRequest(InitializeRequestArguments( adapterID: 'test', supportsRunInTerminalRequest: supportsRunInTerminalRequest, + supportsProgressReporting: supportsProgressReporting, )), sendRequest( SetExceptionBreakpointsArguments(