[dds/dap] Remove breakpoints and resume when trying to terminate test runs

Fixes https://github.com/Dart-Code/Dart-Code/issues/4447.

Change-Id: I9c18fccde101596135d3ea7324bbbd24fb5330c1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/289825
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
index b72a904..4d4685c 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
@@ -178,6 +178,15 @@
   @override
   Future<void> terminateImpl() async {
     terminatePids(ProcessSignal.sigterm);
+
+    // Sending a kill signal to pkg:test doesn't cause it to exit immediately,
+    // instead it waits for the current test to complete. If the user is at
+    // a breakpoint this will never happen, which will encourage them to click
+    // Stop again. In VS Code, a second Stop is a non-graceful shutdown which
+    // may leave orphaned processes. To avoid this, attempt to resume and avoid
+    // any further pausing.
+    await preventBreakingAndResume();
+
     await _process?.exitCode;
   }
 
diff --git a/pkg/dds/test/dap/integration/dart_test_test.dart b/pkg/dds/test/dap/integration/dart_test_test.dart
index c04535c..4f8fac6 100644
--- a/pkg/dds/test/dap/integration/dart_test_test.dart
+++ b/pkg/dds/test/dap/integration/dart_test_test.dart
@@ -124,6 +124,26 @@
       expectStandardSimpleTestResults(outputEvents);
     });
 
+    test('can cleanly terminate from a breakpoint', () async {
+      final client = dap.client;
+      final testFile = dap.createTestFile(simpleTestProgram);
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+
+      // Hit the breakpoint inside the test.
+      await client.hitBreakpoint(
+        testFile,
+        breakpointLine,
+        cwd: dap.testAppDir.path,
+      );
+
+      // Send a single terinate, and expect a clean exit (with a `terminated`
+      // event).
+      await Future.wait([
+        dap.client.event('terminated'),
+        dap.client.terminate(),
+      ], eagerError: true);
+    });
+
     test('rejects attaching', () async {
       final client = dap.client;