[dds] Add support for additional VM args in DAP

Change-Id: Ib5c17e4f1881b3ca2c0fec72cc93178aaada3f60
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/221800
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index 756236e..523cadf 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -1888,6 +1888,15 @@
   /// the VM or Flutter tool).
   final List<String>? toolArgs;
 
+  /// Arguments to be passed directly to the Dart VM that will run [program].
+  ///
+  /// Unlike [toolArgs] which always go after the complete tool, these args
+  /// always go directly after `dart`:
+  ///
+  ///   - dart {vmAdditionalArgs} {toolArgs}
+  ///   - dart {vmAdditionalArgs} run test:test {toolArgs}
+  final List<String>? vmAdditionalArgs;
+
   final int? vmServicePort;
 
   final bool? enableAsserts;
@@ -1928,6 +1937,7 @@
     this.args,
     this.vmServicePort,
     this.toolArgs,
+    this.vmAdditionalArgs,
     this.console,
     this.enableAsserts,
     this.customTool,
@@ -1960,6 +1970,7 @@
         program = obj['program'] as String,
         args = (obj['args'] as List?)?.cast<String>(),
         toolArgs = (obj['toolArgs'] as List?)?.cast<String>(),
+        vmAdditionalArgs = (obj['vmAdditionalArgs'] as List?)?.cast<String>(),
         vmServicePort = obj['vmServicePort'] as int?,
         console = obj['console'] as String?,
         enableAsserts = obj['enableAsserts'] as bool?,
@@ -1974,6 +1985,7 @@
         'program': program,
         if (args != null) 'args': args,
         if (toolArgs != null) 'toolArgs': toolArgs,
+        if (vmAdditionalArgs != null) 'vmAdditionalArgs': vmAdditionalArgs,
         if (vmServicePort != null) 'vmServicePort': vmServicePort,
         if (console != null) 'console': console,
         if (enableAsserts != null) 'enableAsserts': enableAsserts,
diff --git a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
index 405d085..46e28e3 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
@@ -87,6 +87,7 @@
     }
 
     final vmArgs = <String>[
+      ...?args.vmAdditionalArgs,
       if (debug) ...[
         '--enable-vm-service=${args.vmServicePort ?? 0}${ipv6 ? '/::1' : ''}',
         '--pause_isolates_on_start',
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 25cafd7..0f67af4 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
@@ -83,6 +83,7 @@
     }
 
     final vmArgs = <String>[
+      ...?args.vmAdditionalArgs,
       if (debug) ...[
         '--enable-vm-service=${args.vmServicePort ?? 0}${ipv6 ? '/::1' : ''}',
         '--pause_isolates_on_start',
diff --git a/pkg/dds/test/dap/dart_cli_test.dart b/pkg/dds/test/dap/dart_cli_test.dart
index 948aad5..d07dc37 100644
--- a/pkg/dds/test/dap/dart_cli_test.dart
+++ b/pkg/dds/test/dap/dart_cli_test.dart
@@ -12,6 +12,24 @@
 
 main() {
   group('dart cli adapter', () {
+    test('includes vmAdditionalArgs', () async {
+      final adapter = MockDartCliDebugAdapter();
+      final responseCompleter = Completer<void>();
+      final request = MockRequest();
+      final args = DartLaunchRequestArguments(
+        program: 'foo.dart',
+        vmAdditionalArgs: ['vm_arg'],
+        noDebug: true,
+      );
+
+      await adapter.configurationDoneRequest(request, null, () {});
+      await adapter.launchRequest(request, args, responseCompleter.complete);
+      await responseCompleter.future;
+
+      expect(adapter.executable, equals(Platform.resolvedExecutable));
+      expect(adapter.processArgs, containsAllInOrder(['vm_arg', 'foo.dart']));
+    });
+
     test('includes toolArgs', () async {
       final adapter = MockDartCliDebugAdapter();
       final responseCompleter = Completer<void>();
@@ -27,7 +45,7 @@
       await responseCompleter.future;
 
       expect(adapter.executable, equals(Platform.resolvedExecutable));
-      expect(adapter.processArgs, contains('tool_arg'));
+      expect(adapter.processArgs, containsAllInOrder(['tool_arg', 'foo.dart']));
     });
 
     test('includes env', () async {
diff --git a/pkg/dds/test/dap/dart_test_test.dart b/pkg/dds/test/dap/dart_test_test.dart
index 5c04566..defba7d 100644
--- a/pkg/dds/test/dap/dart_test_test.dart
+++ b/pkg/dds/test/dap/dart_test_test.dart
@@ -12,7 +12,28 @@
 
 main() {
   group('dart test adapter', () {
-    test('includes toolArgs', () async {
+    test('includes vmAdditionalArgs before run test:test', () async {
+      final adapter = MockDartTestDebugAdapter();
+      final responseCompleter = Completer<void>();
+      final request = MockRequest();
+      final args = DartLaunchRequestArguments(
+        program: 'foo.dart',
+        vmAdditionalArgs: ['vm_arg'],
+        noDebug: true,
+      );
+
+      await adapter.configurationDoneRequest(request, null, () {});
+      await adapter.launchRequest(request, args, responseCompleter.complete);
+      await responseCompleter.future;
+
+      expect(adapter.executable, equals(Platform.resolvedExecutable));
+      expect(
+        adapter.processArgs,
+        containsAllInOrder(['vm_arg', 'run', 'test:test', 'foo.dart']),
+      );
+    });
+
+    test('includes toolArgs after run test:test', () async {
       final adapter = MockDartTestDebugAdapter();
       final responseCompleter = Completer<void>();
       final request = MockRequest();
@@ -27,8 +48,10 @@
       await responseCompleter.future;
 
       expect(adapter.executable, equals(Platform.resolvedExecutable));
-      expect(adapter.processArgs, containsAllInOrder(['run', 'test:test']));
-      expect(adapter.processArgs, contains('tool_arg'));
+      expect(
+        adapter.processArgs,
+        containsAllInOrder(['run', 'test:test', 'tool_arg', 'foo.dart']),
+      );
     });
 
     test('includes env', () async {
diff --git a/pkg/dds/tool/dap/README.md b/pkg/dds/tool/dap/README.md
index 2edd964..26bbeb3 100644
--- a/pkg/dds/tool/dap/README.md
+++ b/pkg/dds/tool/dap/README.md
@@ -35,8 +35,9 @@
 
 - `bool? noDebug` - whether to run in debug or noDebug mode (if not supplied, defaults to debug)
 - `String program` - the path of the Dart program to run
-- `List<String>? args` - arguments to be passed to the Dart program
-- `List<String>? toolArgs` - arguments for the Dart VM
+- `List<String>? args` - arguments to be passed to the Dart program (after the `program` on the command line)
+- `List<String>? toolArgs` - arguments passed after the tool that will run `program` (after `dart` for CLI scripts and after `dart run test:test` for test scripts)
+- `List<String>? vmAdditionalArgs` - arguments passed directly to the Dart VM (after `dart` for both CLI scripts and test scripts)
 - `String? console` - if set to `"terminal"` or `"externalTerminal"` will be run using the `runInTerminal` reverse-request; otherwise the debug adapter spawns the Dart process
 - `bool? enableAsserts` - whether to enable asserts (if not supplied, defaults to enabled)
 - `String? customTool` - an optional tool to run instead of `dart` - the custom tool must be completely compatible with the tool/command it is replacing