[vm_service] Get tester example functional and clean up

Makes adjustments so `example/vm_service_tester.dart` runs successfully, and complete some clean up to make the code and comments in the file more consistent.

Closes https://github.com/dart-lang/sdk/issues/55753

TEST=N/A
Change-Id: Ib91985792a25a27008c2ef96786a40bb7a26ad57
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/374242
Reviewed-by: Derek Xu <derekx@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/vm_service/example/vm_service_tester.dart b/pkg/vm_service/example/vm_service_tester.dart
index 54edcee..3311b7f 100644
--- a/pkg/vm_service/example/vm_service_tester.dart
+++ b/pkg/vm_service/example/vm_service_tester.dart
@@ -2,7 +2,8 @@
 // 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.
 
-library service_tester;
+/// An example of using the the libraries provided by `package:vm_service`.
+library;
 
 import 'dart:async';
 import 'dart:collection';
@@ -14,8 +15,8 @@
 import 'package:vm_service/vm_service.dart';
 import 'package:vm_service/vm_service_io.dart';
 
-final String host = 'localhost';
-final int port = 7575;
+const String host = 'localhost';
+const int port = 7575;
 
 late VmService serviceClient;
 
@@ -27,35 +28,40 @@
   });
 
   test('integration', () async {
-    String sdk = path.dirname(path.dirname(Platform.resolvedExecutable));
-
-    print('Using sdk at $sdk.');
+    final sdkPath = path.dirname(path.dirname(Platform.resolvedExecutable));
+    print('Using sdk at $sdkPath.');
 
     // pause_isolates_on_start, pause_isolates_on_exit
-    process = await Process.start('$sdk/bin/dart', [
-      '--pause_isolates_on_start',
-      '--enable-vm-service=$port',
-      '--disable-service-auth-codes',
-      'example/sample_main.dart'
-    ]);
+    final sampleProcess = process = await Process.start(
+      Platform.resolvedExecutable,
+      [
+        '--pause-isolates-on-start',
+        '--enable-vm-service=$port',
+        '--disable-service-auth-codes',
+        'example/sample_main.dart',
+      ],
+    );
 
-    print('dart process started');
+    print('Dart process started.');
 
-    unawaited(process!.exitCode.then((code) => print('vm exited: $code')));
-    process!.stdout.transform(utf8.decoder).listen(print);
-    process!.stderr.transform(utf8.decoder).listen(print);
+    unawaited(sampleProcess.exitCode.then((code) => print('vm exited: $code')));
+    sampleProcess.stdout.transform(utf8.decoder).listen(print);
+    sampleProcess.stderr.transform(utf8.decoder).listen(print);
 
-    await Future.delayed(Duration(milliseconds: 500));
+    await Future.delayed(const Duration(milliseconds: 500));
 
-    final wsUri = 'ws://$host$port/ws';
-    serviceClient = await vmServiceConnectUri(wsUri, log: StdoutLog());
+    final wsUri = Uri(scheme: 'ws', host: host, port: port, path: 'ws');
+    serviceClient = await vmServiceConnectUri(
+      wsUri.toString(),
+      log: StdoutLog(),
+    );
 
-    print('socket connected');
+    print('VM service web socket connected.');
 
     serviceClient.onSend.listen((str) => print('--> $str'));
 
-    // The next listener will bail out if you toggle this to false, which we need
-    // to do for some things like the custom service registration tests.
+    // The next listener will bail out if you toggle this to false, which is
+    // needed for some things like the custom service registration tests.
     var checkResponseJsonCompatibility = true;
     serviceClient.onReceive.listen((str) {
       print('<-- $str');
@@ -65,25 +71,41 @@
       // For each received event, check that we can deserialize it and
       // reserialize it back to the same exact representation (minus private
       // fields).
-      var json = jsonDecode(str);
+      final json = jsonDecode(str);
       var originalJson = json['result'] as Map<String, dynamic>?;
       if (originalJson == null && json['method'] == 'streamNotify') {
         originalJson = json['params']['event'];
       }
       expect(originalJson, isNotNull, reason: 'Unrecognized event type! $json');
 
-      var instance =
-          createServiceObject(originalJson, const ['Event', 'Success']);
+      final instance =
+          createServiceObject(originalJson!, const ['Event', 'Success']);
       expect(instance, isNotNull,
-          reason: 'failed to deserialize object $originalJson!');
+          reason: 'Failed to deserialize object $originalJson!');
 
-      var reserializedJson = (instance as dynamic).toJson();
+      final reserializedJson = (instance as dynamic).toJson();
 
-      forEachNestedMap(originalJson!, (obj) {
-        // Private fields that we don't reproduce
+      forEachNestedMap(originalJson, (obj) {
+        // Remove private fields that we don't reproduce.
         obj.removeWhere((k, v) => k.startsWith('_'));
-        // Extra fields that aren't specified and we don't reproduce
+
+        // Remove extra fields that aren't specified and we don't reproduce.
         obj.remove('isExport');
+        obj.remove('isolate_group');
+        obj.remove('parameterizedClass');
+
+        // Convert `Null` instances in the original JSON to
+        // just `null` as `createServiceObject` will use `null`
+        // to represent the reference.
+        obj.updateAll((key, value) {
+          if (value is Map &&
+              value['type'] == '@Instance' &&
+              value['kind'] == 'Null') {
+            return null;
+          } else {
+            return value;
+          }
+        });
       });
 
       forEachNestedMap(reserializedJson, (obj) {
@@ -104,14 +126,14 @@
     unawaited(serviceClient.streamListen(EventStreams.kDebug));
     unawaited(serviceClient.streamListen(EventStreams.kStdout));
 
-    VM vm = await serviceClient.getVM();
+    final vm = await serviceClient.getVM();
     print('hostCPU=${vm.hostCPU}');
     print(await serviceClient.getVersion());
-    List<IsolateRef> isolates = vm.isolates!;
+    final isolates = vm.isolates!;
     print(isolates);
 
-    // Disable the json reserialization checks since custom services are not
-    // supported.
+    // Disable the json reserialization checks since custom services are
+    // not supported.
     checkResponseJsonCompatibility = false;
     await testServiceRegistration();
     checkResponseJsonCompatibility = true;
@@ -119,23 +141,23 @@
     await testScriptParse(vm.isolates!.first);
     await testSourceReport(vm.isolates!.first);
 
-    IsolateRef isolateRef = isolates.first;
+    final isolateRef = isolates.first;
     print(await serviceClient.resume(isolateRef.id!));
 
-    print('waiting for client to shut down...');
+    print('Waiting for service client to shut down...');
     await serviceClient.dispose();
 
     await serviceClient.onDone;
-    print('service client shut down');
+    print('Service client shut down.');
   });
 }
 
-// Deeply traverses a map and calls [cb] with each nested map and the
-// parent map.
-void forEachNestedMap(Map input, Function(Map) cb) {
-  var queue = Queue.from([input]);
+/// Deeply traverses the [input] map and calls [cb] with
+/// each nested map and the parent map.
+void forEachNestedMap(Map input, void Function(Map) cb) {
+  final queue = Queue.from([input]);
   while (queue.isNotEmpty) {
-    var next = queue.removeFirst();
+    final next = queue.removeFirst();
     if (next is Map) {
       cb(next);
       queue.addAll(next.values);
@@ -145,7 +167,7 @@
   }
 }
 
-Future testServiceRegistration() async {
+Future<void> testServiceRegistration() async {
   const String serviceName = 'serviceName';
   const String serviceAlias = 'serviceAlias';
   const String movedValue = 'movedValue';
@@ -157,15 +179,18 @@
     };
   });
   await serviceClient.registerService(serviceName, serviceAlias);
-  final wsUri = 'ws://$host$port/ws';
-  VmService otherClient = await vmServiceConnectUri(wsUri, log: StdoutLog());
-  Completer completer = Completer();
+  final wsUri = Uri(scheme: 'ws', host: host, port: port, path: 'ws');
+  final otherClient = await vmServiceConnectUri(
+    wsUri.toString(),
+    log: StdoutLog(),
+  );
+  final completer = Completer();
   otherClient.onEvent('Service').listen((e) async {
     if (e.service == serviceName && e.kind == EventKind.kServiceRegistered) {
       assert(e.alias == serviceAlias);
-      Response? response = await serviceClient.callMethod(
+      final response = await serviceClient.callMethod(
         e.method!,
-        args: <String, dynamic>{'input': movedValue},
+        args: {'input': movedValue},
       );
       assert(response.json!['output'] == movedValue);
       completer.complete();
@@ -176,14 +201,14 @@
   await otherClient.dispose();
 }
 
-Future testScriptParse(IsolateRef isolateRef) async {
+Future<void> testScriptParse(IsolateRef isolateRef) async {
   final isolateId = isolateRef.id!;
-  final Isolate isolate = await serviceClient.getIsolate(isolateId);
-  final Library rootLibrary =
+  final isolate = await serviceClient.getIsolate(isolateId);
+  final rootLibrary =
       await serviceClient.getObject(isolateId, isolate.rootLib!.id!) as Library;
-  final ScriptRef scriptRef = rootLibrary.scripts!.first;
+  final scriptRef = rootLibrary.scripts!.first;
 
-  final Script script =
+  final script =
       await serviceClient.getObject(isolateId, scriptRef.id!) as Script;
   print(script);
   print(script.uri);
@@ -192,21 +217,23 @@
   print(script.tokenPosTable!.length);
 }
 
-Future testSourceReport(IsolateRef isolateRef) async {
+Future<void> testSourceReport(IsolateRef isolateRef) async {
   final isolateId = isolateRef.id!;
-  final Isolate isolate = await serviceClient.getIsolate(isolateId);
-  final Library rootLibrary =
+  final isolate = await serviceClient.getIsolate(isolateId);
+  final rootLibrary =
       await serviceClient.getObject(isolateId, isolate.rootLib!.id!) as Library;
-  final ScriptRef scriptRef = rootLibrary.scripts!.first;
+  final scriptRef = rootLibrary.scripts!.first;
 
-  // make sure some code has run
+  // Make sure that some code has run.
   await serviceClient.resume(isolateId);
   await Future.delayed(const Duration(milliseconds: 25));
 
-  final SourceReport sourceReport = await serviceClient.getSourceReport(
-      isolateId, [SourceReportKind.kCoverage],
-      scriptId: scriptRef.id);
-  for (SourceReportRange range in sourceReport.ranges!) {
+  final sourceReport = await serviceClient.getSourceReport(
+    isolateId,
+    [SourceReportKind.kCoverage],
+    scriptId: scriptRef.id,
+  );
+  for (final range in sourceReport.ranges!) {
     print('  $range');
     if (range.coverage != null) {
       print('  ${range.coverage}');
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index 0e4391b..6eb853f 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -2512,7 +2512,6 @@
 ///
 /// If the field is uninitialized, the `value` will be the `NotInitialized`
 /// [Sentinel].
-///
 class BoundField {
   static BoundField? parse(Map<String, dynamic>? json) =>
       json == null ? null : BoundField._fromJson(json);
@@ -3073,10 +3072,15 @@
   /// What kind of code object is this?
   /*CodeKind*/ String? kind;
 
+  /// This code object's corresponding function.
+  @optional
+  FuncRef? function;
+
   CodeRef({
     this.name,
     this.kind,
     required String id,
+    this.function,
   }) : super(
           id: id,
         );
@@ -3084,6 +3088,8 @@
   CodeRef._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
     name = json['name'] ?? '';
     kind = json['kind'] ?? '';
+    function =
+        createServiceObject(json['function'], const ['FuncRef']) as FuncRef?;
   }
 
   @override
@@ -3097,6 +3103,7 @@
       'name': name ?? '',
       'kind': kind ?? '',
     });
+    _setIfNotNull(json, 'function', function?.toJson());
     return json;
   }
 
@@ -3123,10 +3130,16 @@
   @override
   /*CodeKind*/ String? kind;
 
+  /// This code object's corresponding function.
+  @optional
+  @override
+  FuncRef? function;
+
   Code({
     this.name,
     this.kind,
     required String id,
+    this.function,
   }) : super(
           id: id,
         );
@@ -3134,6 +3147,8 @@
   Code._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
     name = json['name'] ?? '';
     kind = json['kind'] ?? '';
+    function =
+        createServiceObject(json['function'], const ['FuncRef']) as FuncRef?;
   }
 
   @override
@@ -3147,6 +3162,7 @@
       'name': name ?? '',
       'kind': kind ?? '',
     });
+    _setIfNotNull(json, 'function', function?.toJson());
     return json;
   }
 
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index 8c9e858..5c714ec 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -2085,6 +2085,9 @@
 
   // What kind of code object is this?
   CodeKind kind;
+
+  // This code object's corresponding function.
+  @Function function [optional];
 }
 ```
 
@@ -2097,6 +2100,9 @@
 
   // What kind of code object is this?
   CodeKind kind;
+
+  // This code object's corresponding function.
+  @Function function [optional];
 }
 ```
 
@@ -3188,7 +3194,7 @@
   //   RegExp
   @Instance pattern [optional];
 
-// The function associated with a Closure instance.
+  // The function associated with a Closure instance.
   //
   // Provided for instance kinds:
   //   Closure