[ Service ] Test constructor tearoffs through VM service

Fixes #46240

TEST=runtime/observatory/tests/service/constructor_tear_off_test.dart

Change-Id: I335b16106db89dfede10d2ba3b73676465b306bc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/212627
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/runtime/observatory/lib/src/models/objects/library.dart b/runtime/observatory/lib/src/models/objects/library.dart
index b02a97d..dfb3f84 100644
--- a/runtime/observatory/lib/src/models/objects/library.dart
+++ b/runtime/observatory/lib/src/models/objects/library.dart
@@ -41,5 +41,5 @@
   LibraryRef get target;
 
   /// [optional]
-  String get prefix;
+  String? get prefix;
 }
diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart
index df4a4cd..9878a02 100644
--- a/runtime/observatory/lib/src/service/object.dart
+++ b/runtime/observatory/lib/src/service/object.dart
@@ -2479,7 +2479,7 @@
 class LibraryDependency implements M.LibraryDependency {
   final bool isImport;
   final bool isDeferred;
-  final String prefix;
+  final String? prefix;
   final Library target;
 
   bool get isExport => !isImport;
diff --git a/runtime/observatory/tests/service/constructor_tear_off_test.dart b/runtime/observatory/tests/service/constructor_tear_off_test.dart
new file mode 100644
index 0000000..672a1a1
--- /dev/null
+++ b/runtime/observatory/tests/service/constructor_tear_off_test.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+// @dart=2.15
+
+// SharedOptions=--enable-experiment=constructor-tearoffs
+
+import 'package:observatory/service_common.dart';
+import 'package:test/test.dart';
+
+import 'test_helper.dart';
+
+class Foo {
+  Foo();
+  Foo.named();
+}
+
+class Generic<T> {
+  Generic();
+}
+
+@pragma('vm:entry-point')
+Function getNamedConstructorTearoff() => Foo.named;
+
+@pragma('vm:entry-point')
+Function getDefaultConstructorTearoff() => Foo.new;
+
+@pragma('vm:entry-point')
+Function getGenericConstructorTearoff() => Generic<int>.new;
+
+Future<void> invokeConstructorTearoff(
+  Isolate isolate,
+  String name,
+  String expectedType,
+) async {
+  final lib = await isolate.rootLibrary.load() as Library;
+  final tearoff = await isolate.invokeRpc('invoke', {
+    'targetId': lib.id,
+    'selector': name,
+    'argumentIds': [],
+  });
+  final result = await isolate.invokeRpc('invoke', {
+    'targetId': tearoff.id,
+    'selector': 'call',
+    'argumentIds': [],
+  }) as Instance;
+  expect(result.clazz!.name, expectedType);
+}
+
+final tests = <IsolateTest>[
+  (Isolate isolate) => invokeConstructorTearoff(
+        isolate,
+        'getNamedConstructorTearoff',
+        'Foo',
+      ),
+  (Isolate isolate) => invokeConstructorTearoff(
+        isolate,
+        'getDefaultConstructorTearoff',
+        'Foo',
+      ),
+  (Isolate isolate) => invokeConstructorTearoff(
+        isolate,
+        'getGenericConstructorTearoff',
+        'Generic',
+      ),
+];
+
+void main(List<String> args) => runIsolateTests(
+      args,
+      tests,
+    );