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 68baed6822..70e842a49a 100644 --- a/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart +++ b/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart @@ -205,6 +205,9 @@ class FlutterDebugAdapter extends DartDebugAdapter disconnectImpl() async { + if (isAttach) { + await preventBreakingAndResume(); + } terminatePids(ProcessSignal.sigkill); } @@ -379,6 +382,9 @@ class FlutterDebugAdapter extends DartDebugAdapter terminateImpl() async { + if (isAttach) { + await preventBreakingAndResume(); + } terminatePids(ProcessSignal.sigterm); await _process?.exitCode; } diff --git a/packages/flutter_tools/test/integration.shard/debug_adapter/flutter_adapter_test.dart b/packages/flutter_tools/test/integration.shard/debug_adapter/flutter_adapter_test.dart index 6ab8cb1987..4e63805e5d 100644 --- a/packages/flutter_tools/test/integration.shard/debug_adapter/flutter_adapter_test.dart +++ b/packages/flutter_tools/test/integration.shard/debug_adapter/flutter_adapter_test.dart @@ -380,13 +380,35 @@ void main() { stoppedFuture, dap.client.setBreakpoint(breakpointFilePath, breakpointLine), ], eagerError: true); - final int threadId = (await stoppedFuture).threadId!; + }); - // Remove the breakpoint and resume. - await dap.client.clearBreakpoints(breakpointFilePath); - await dap.client.continue_(threadId); + testWithoutContext('resumes and removes breakpoints on detach', () async { + final Uri vmServiceUri = await testProcess.vmServiceUri; + // Launch the app and wait for it to print "topLevelFunction". + await Future.wait(>[ + dap.client.stdoutOutput.firstWhere((String output) => output.startsWith('topLevelFunction')), + dap.client.start( + launch: () => dap.client.attach( + cwd: project.dir.path, + toolArgs: ['-d', 'flutter-tester'], + vmServiceUri: vmServiceUri.toString(), + ), + ), + ], eagerError: true); + + // Set a breakpoint and expect to hit it. + final Future stoppedFuture = dap.client.stoppedEvents.firstWhere((StoppedEventBody e) => e.reason == 'breakpoint'); + await Future.wait(>[ + stoppedFuture, + dap.client.setBreakpoint(breakpointFilePath, breakpointLine), + ], eagerError: true); + + // Detach. await dap.client.terminate(); + + // Ensure we get additional output (confirming the process resumed). + await testProcess.output.first; }); }); } diff --git a/packages/flutter_tools/test/integration.shard/debug_adapter/test_support.dart b/packages/flutter_tools/test/integration.shard/debug_adapter/test_support.dart index dc87cc6459..9cafabfdd9 100644 --- a/packages/flutter_tools/test/integration.shard/debug_adapter/test_support.dart +++ b/packages/flutter_tools/test/integration.shard/debug_adapter/test_support.dart @@ -63,6 +63,11 @@ class SimpleFlutterRunner { unawaited(process.exitCode.then(_handleExitCode)); } + final StreamController _output = StreamController.broadcast(); + + /// A broadcast stream of any non-JSON output from the process. + Stream get output => _output.stream; + void _handleExitCode(int code) { if (!_vmServiceUriCompleter.isCompleted) { _vmServiceUriCompleter.completeError('Flutter process ended without producing a VM Service URI'); @@ -91,8 +96,9 @@ class SimpleFlutterRunner { } } } on FormatException { - // `flutter run` writes a lot of text to stdout so just ignore anything - // that's not valid JSON. + // `flutter run` writes a lot of text to stdout that isn't daemon messages + // (not valid JSON), so just pass that one for tests that may want it. + _output.add(outputLine); } }