[ CLI ] Implicitly set `--mark-main-isolate-as-system-isolate` for `dart test`

Fixes https://github.com/flutter/flutter/issues/143170

TEST=test_test.dart

Change-Id: I98044ab2362adcdd55c4f6def0752a755b1385b8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/366040
Reviewed-by: Derek Xu <derekx@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
Reviewed-by: Kenzie Davisson <kenzieschmoll@google.com>
diff --git a/pkg/dartdev/lib/src/commands/test.dart b/pkg/dartdev/lib/src/commands/test.dart
index 19f072ab..637c764 100644
--- a/pkg/dartdev/lib/src/commands/test.dart
+++ b/pkg/dartdev/lib/src/commands/test.dart
@@ -67,8 +67,17 @@
           .where((e) => !e.startsWith('--$experimentFlagName='))
           .toList();
       log.trace('dart $testExecutable ${argsRestNoExperiment.join(' ')}');
-      VmInteropHandler.run(testExecutable.executable, argsRestNoExperiment,
-          packageConfigOverride: testExecutable.packageConfig!);
+      VmInteropHandler.run(
+        testExecutable.executable,
+        argsRestNoExperiment,
+        packageConfigOverride: testExecutable.packageConfig!,
+        // TODO(bkonyi): remove once DartDev moves to AOT and this flag can be
+        // provided directly to the process spawned by `dart run` and
+        // `dart test`.
+        //
+        // See https://github.com/dart-lang/sdk/issues/53576
+        markMainIsolateAsSystemIsolate: true,
+      );
       return 0;
     } on CommandResolutionFailedException catch (e) {
       if (project.hasPubspecFile) {
diff --git a/pkg/dartdev/lib/src/vm_interop_handler.dart b/pkg/dartdev/lib/src/vm_interop_handler.dart
index ab6f189..37eed1d 100644
--- a/pkg/dartdev/lib/src/vm_interop_handler.dart
+++ b/pkg/dartdev/lib/src/vm_interop_handler.dart
@@ -16,13 +16,17 @@
   ///
   /// If [packageConfigOverride] is given, that is where the packageConfig is found.
   ///
-  /// If [forceNoSoundNullSafety] is given and set to true, the spawned isolate will run
-  /// with `--no-sound-null-safety` enabled.
+  /// If [markMainIsolateAsSystemIsolate] is given and set to true, the spawned
+  /// isolate will run with `--mark-main-isolate-as-system-isolate` enabled.
   static void run(
     String script,
     List<String> args, {
     String? packageConfigOverride,
-    bool forceNoSoundNullSafety = false,
+    // TODO(bkonyi): remove once DartDev moves to AOT and this flag can be
+    // provided directly to the process spawned by `dart run` and `dart test`.
+    //
+    // See https://github.com/dart-lang/sdk/issues/53576
+    bool markMainIsolateAsSystemIsolate = false,
   }) {
     final port = _port;
     if (port == null) return;
@@ -30,7 +34,7 @@
       _kResultRun,
       script,
       packageConfigOverride,
-      forceNoSoundNullSafety,
+      markMainIsolateAsSystemIsolate,
       // Copy the list so it doesn't get GC'd underneath us.
       args.toList()
     ];
diff --git a/pkg/dartdev/test/commands/test_test.dart b/pkg/dartdev/test/commands/test_test.dart
index 8abeecc..e7bcd7b 100644
--- a/pkg/dartdev/test/commands/test_test.dart
+++ b/pkg/dartdev/test/commands/test_test.dart
@@ -2,11 +2,14 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:async';
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:path/path.dart' as path;
 import 'package:pub_semver/pub_semver.dart';
 import 'package:test/test.dart';
+import 'package:vm_service/vm_service_io.dart';
 
 import '../experiment_util.dart';
 import '../utils.dart';
@@ -157,6 +160,53 @@
     expect(result.stderr, isEmpty);
   });
 
+  test('implicitly passes --mark-main-isolate-as-system-isolate', () async {
+    // --mark-main-isolate-as-system-isolate is necessary for DevTools to be
+    // able to identify the correct root library.
+    //
+    // See https://github.com/flutter/flutter/issues/143170 for details.
+    final p = project(
+      mainSrc: 'int get foo => 1;\n',
+      pubspecExtras: {
+        'dev_dependencies': {'test': 'any'}
+      },
+    );
+    p.file('test/foo_test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+  test('', () {
+    print('hello world');
+  });
+}
+''');
+
+    final vmServiceUriRegExp =
+        RegExp(r'(http:\/\/127.0.0.1:\d*\/[\da-zA-Z-_]*=\/)');
+    final process = await p.start(['test', '--pause-after-load']);
+    final completer = Completer<Uri>();
+    late StreamSubscription sub;
+    sub = process.stdout
+        .transform(utf8.decoder)
+        .transform(const LineSplitter())
+        .listen((line) async {
+      if (line.contains(vmServiceUriRegExp)) {
+        await sub.cancel();
+        final httpUri = Uri.parse(
+          vmServiceUriRegExp.firstMatch(line)!.group(0)!,
+        );
+        completer.complete(
+          httpUri.replace(scheme: 'ws', path: '${httpUri.path}ws'),
+        );
+      }
+    });
+
+    final vmServiceUri = await completer.future;
+    final vmService = await vmServiceConnectUri(vmServiceUri.toString());
+    final vm = await vmService.getVM();
+    expect(vm.systemIsolates!.where((e) => e.name == 'main'), isNotEmpty);
+  });
+
   group('--enable-experiment', () {
     late TestProject p;
     Future<ProcessResult> runTestWithExperimentFlag(String? flag) async {
diff --git a/runtime/bin/dartdev_isolate.cc b/runtime/bin/dartdev_isolate.cc
index d58608b..6851965 100644
--- a/runtime/bin/dartdev_isolate.cc
+++ b/runtime/bin/dartdev_isolate.cc
@@ -14,6 +14,7 @@
 #include "bin/exe_utils.h"
 #include "bin/file.h"
 #include "bin/lockers.h"
+#include "bin/main_options.h"
 #include "bin/platform.h"
 #include "bin/process.h"
 #include "include/dart_embedder_api.h"
@@ -160,6 +161,10 @@
       auto item3 = GetArrayItem(message, 3);
 
       ASSERT(item3->type == Dart_CObject_kBool);
+      const bool mark_main_isolate_as_system_isolate = item3->value.as_bool;
+      if (mark_main_isolate_as_system_isolate) {
+        Options::set_mark_main_isolate_as_system_isolate(true);
+      }
 
       if (*script_ != nullptr) {
         free(*script_);
diff --git a/runtime/bin/main_options.h b/runtime/bin/main_options.h
index a80314e..8b5e6eb 100644
--- a/runtime/bin/main_options.h
+++ b/runtime/bin/main_options.h
@@ -150,6 +150,14 @@
   static const char* vm_service_server_ip() { return vm_service_server_ip_; }
   static int vm_service_server_port() { return vm_service_server_port_; }
 
+  // TODO(bkonyi): remove once DartDev moves to AOT and this flag can be
+  // provided directly to the process spawned by `dart run` and `dart test`.
+  //
+  // See https://github.com/dart-lang/sdk/issues/53576
+  static void set_mark_main_isolate_as_system_isolate(bool state) {
+    mark_main_isolate_as_system_isolate_ = state;
+  }
+
   static Dart_KernelCompilationVerbosityLevel verbosity_level() {
     return VerbosityLevelToDartAPI(verbosity_);
   }